Let's assume I have a function with many arguments, e.g.
import pandas as pd
def f(df: pd.DataFrame, a: int, b: int, c: int, d: int, inplace: bool = True) -> Optional[pd.DataFrame]:
raise NotImplementedError
The function will modify the dataframe if inplace=True and return a modified copy if inplace=False.
I know I can do
@overload
def f(df: pd.DataFrame, a: int, b: int, c: int, d: int, inplace: Literal[True] = True) -> None:
...
@overload
def f(df: pd.DataFrame, a: int, b: int, c: int, d: int, inplace: Literal[False] = True) -> pd.DataFrame:
...
to inform the typing system about this fact.
However, I'm wondering if there's a way to do this without repeating the entire function definition, which seems cumbersome if there are many arguments. I'm looking for something like
@overload
def f(..., inplace: Literal[True]) -> None:
...
@overload
def f(..., inplace: Literal[False]) -> pd.DataFrame:
...
EDIT: Several persons have made the point that the API design itself may be flawed. While this may be true, in this particular case I am retrospectively adding type hints to an existing library and changing the interface is not an option.
One function with a boolean flag to alter its behavior like this is an anti-pattern. There should be two functions: one that modifies in-place and returns None, the other that returns a modified copy. (Cf. list.sort and sorted.)
That doesn't mean you need to duplicate behavior; it just means one of the functions will be a thin wrapper around the other.
import pandas as pd
def inplace_f(df: pd.DataFrame, a: int, b: int, c: int, d: int) -> None:
...
def f(df: pd.DataFrame, a: int, b: int, c: int, d:int) -> pd.DataFrame:
copy = df.copy()
inplace_f(copy)
return copy
Now, you just use the former Boolean argument to decide which function to use. That is, for example,
f(df, ..., inplace)
becomes
(inplace_f if inplace else f)(df, ...)
If your objection is that this is more complicated than the "simple" call, then you are ignoring the fact that the "simple" call isn't that simple, because it clearly doesn't make sense to call f with inplace=False if you aren't doing anything with the return value. Context already means you have some notion of what the value of inplace is, so it's not going to be any harder to pick the right function than it will be to supply a Boolean argument to the same function.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With