DefaultArg

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.

This entry was posted in Python and tagged , . Bookmark the permalink.