In python, I have a function to read bytes from a file:
def read_bytes(path: pathlib.Path, tohex: bool = True, hexformat: Optional[Callable] = None) -> bytes | hex | Any:
In this function, hex is whether to convert the bytes to hex. hexformat is a callable which formats the hex.
For example:
read_bytes(pathlib.Path("myfile.txt"), tohex=True, hexformat = str.upper)
Yes, this function does more than one thing, which is bad practice. However, this function is purely theoretical. I was just trying to come up with an easy example of a function with two arguments that must exist together.
Logically, you cannot pass one of these arguments but not the other. You must pass both or neither. So, if this is the case, I want to raise an error:
def read_bytes(path: pathlib.Path, hex: bool = True, hexformat: Optional[Callable] = None) -> bytes | hex | Any:
if hex and hexformat is not None:
raise TypeError("hex and hexformat are ___")
However, I don't know what word to use (I put ___ as a placeholder). What is the terminology for this?
Edit:
I have another problem with this concept: If one of the parameters is a boolean has a default, how should I specify it in the signature?
For example, say I replace hexformat with toupper. toupper is a bool and it defaults to False. Should I specify that like this:
def read_bytes(path: pathlib.Path, tohex: bool = True, toupper: bool = False) -> bytes | hex | Any:
or like this:
def read_bytes(path: pathlib.Path, tohex: bool = True, toupper: bool = None) -> bytes | hex | Any:
if toupper is None:
toupper = False
In the former, I cannot check if the caller explicitly passed in toupper but set tohex to False, and raise an error if this is the case (since toupper has a default). On the other hand, the latter is more verbose.
Which is better?
In general, when different parameters are dependent on each other as you're describing, my tendency is to combine them so that mutually incompatible combinations are simply not possible within the signature of the function.
For example, I might write:
as:
Since there are logically three ways to format the output, it's much more straightforward to express that as an Enum with three values than two bools (which have four possible combinations) or worse, two optional bools (which have nine possible combinations, only three of which will be considered valid).
Another option is to use
typing.overloadto enumerate the possible combinations. This is more complicated, but it has the benefit that if the return type depends on the argument type, it's possible to express that:When you use a static type checker, calls to the function are checked against the
@overloads and you get an error if the call doesn't match any of them: