Default argument values in python are usually used in the following fashion:
def func(foo, bar=4): return foo*bar
However, if the default value for the function is a mutable object (such as a list), changing it will cause subsequent calls to the function behave differently. This behavior produced the following idiom:
def foo(arg = None): if arg is None: arg = [] #modify arg... return arg
This idiom works quite well, unless None is a legitimate value for arg
. consider the follwing:
class Vehicle(object): #other code goes here... def is_car(self, car_type = None): if car_type is None: return self._type == CAR return self._type == CAR and self.value == car_type
The method is_car has two different behaviors: If called without car_type, it will check whether self has _type CAR. Otherwise, it will also check that self has a value of the specific car_type. This will work quite well, but if None is a legitimate car_type, checking against it will produce surprising results – it will only check if self has _type CAR. To avoid this kind of bug, I propose using DefaultArg, a new singleton that will signify that a given argument is “the default argument” for the function. Here is the previous example, now using DefaultArg:
class Vehicle(object): #other code goes here... def is_car(self, car_type = DefaultArg): if car_type is DefaultArg: return self._type == CAR return self._type == CAR and self.value == car_type
This makes sense, because one should never set a variable to DefaultArg (unless through a function call). Also, tools like PyLint can easily check for this kind of mistakes. Note that DefaultArg addresses a specific problem – when the default argument changes the behavior of the function and that None is also a legitimate value for that argument. I would also like to note that DefaultArg was the solution for a real bug I encountred – and now I it use regularly.
Here is a simple implementation, using Daniel Brodie’s Singleton recipe:
class Singleton(type): def __init__(self, *args): type.__init__(self, *args) self._instances = {} def __call__(self, *args): if not args in self._instances: self._instances[args] = type.__call__(self, *args) return self._instances[args] class _DefaultArg(object): __metaclass__=Singleton def __init__(self, *args): pass def __eq__(self, other): return isinstance(other, _DefaultArg) def __ne__(self, other): return not self == other DefaultArg = _DefaultArg()
For the interested reader – Tomer Filiba proposed a different solution (to a slightly different problem I belive), using the “copydefaults” decorator.
Leave a Reply