The recipe presents a simple decorator for function overloading in python. The @overloaded function searches for the first overloads that doesn't raise TypeError when called. Overloads are added to the overloads list by using the @func.overload_with decorator. The order of function definition determines which function gets tried first and once it founds a compatible function, it skips the rest of the overloads list.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | #!/usr/bin/env python
from functools import wraps
def overloaded(func):
@wraps(func)
def overloaded_func(*args, **kwargs):
for f in overloaded_func.overloads:
try:
return f(*args, **kwargs)
except TypeError:
pass
else:
# it will be nice if the error message prints a list of
# possible signatures here
raise TypeError("No compatible signatures")
def overload_with(func):
overloaded_func.overloads.append(func)
return overloaded_func
overloaded_func.overloads = [func]
overloaded_func.overload_with = overload_with
return overloaded_func
#############
@overloaded
def foo():
print 'foo() without args'
pass
@foo.overload_with
def _(n):
# note that, like property(), the function's name in
# the "def _(n):" line can be arbitrary, the important
# name is in the "@overloads(a)" line
print 'foo() with one argument'
pass
foo()
foo(4)
foo(4, 5) # ERROR: no matching signature
|
In the past, tricks using default arguments such as:
def foo(n=None):
if n is None: n = 10
return n + 20
has been used and has become a popular idiom; this decorator is meant to replace a subset of that idiom.
The use of function overloading clearly separates each signature's code and completely prevents code in one signature from interfering code in another signature. It also makes for smaller functions body, as each function only cares about its own signature.
Limitations: The decorator doesn't do type-checking; it can only classifies overloads by the number of arguments. An alternate implementations could do some type checking so it can classify by the type of the arguments as well.
Known Issues: The decorator relies on catching TypeError, therefore if the underlying code raises TypeError... nobody knows what might happen.
This version is the simpler recipe for the type-checking version here: http://code.activestate.com/recipes/577065-type-checking-function-overloading-decorator/
Perhaps a version that checks the number of arguments before calling the function would be safer (so you don't have to rely on TypeError, are unaffected by other random TypeErrors, and functions with side effects aren't wrongly called).
inspect.getargspec might be useful in doing that.