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.