Reputation: 11755
I have a function with one optional argument, like this:
def funA(x, a, b=1):
return a+b*x
I want to write a new function that calls funA
and also has an optional argument, but if no argument is passed, I want to keep the default in funA
.
I was thinking something like this:
def funB(x, a, b=None):
if b:
return funA(x, a, b)
else:
return funA(x, a)
Is there a more pythonic way of doing this?
Upvotes: 13
Views: 3677
Reputation: 40703
Using FunctionType
from types
, you can just take a function and create a new one specifying the defaults at runtime. You can put all this in a decorator so that at the point of where you write your code it will keep things tidy, whilst still giving the reader a clue about what you are trying to accomplish. It also allows the exact same call signature for funB
as funA
-- all arguments can be positional, or all arguments can be keywords, or any valid mix thereof, and any arguments with default values are optional. Should play nice with positional arguments (*args
) and keyword arguments (**kwargs
) too.
import inspect
from types import FunctionType
def copy_defaults(source_function):
def decorator(destination_function):
"""Creates a wrapper for the destination function with the exact same
signature as source_function (including defaults)."""
# check signature matches
src_sig = inspect.signature(source_function)
dst_sig = inspect.signature(destination_function)
if list(src_sig.parameters) != list(dst_sig.parameters):
raise ValueError("src func and dst func do not having matching " \
"parameter names / order")
return FunctionType(
destination_function.__code__,
destination_function.__globals__,
destination_function.__name__,
source_function.__defaults__, # use defaults from src
destination_function.__closure__
)
return decorator
def funA(x, a, b=1):
return a+b*x
@copy_defaults(funA)
def funB(x, a, b):
"""this is fun B"""
return funA(x, a, b)
assert funA(1, 2) == funB(1, 2)
assert funB.__name__ == "funB"
assert funB.__doc__ == "this is fun B"
Upvotes: 2
Reputation: 369044
Using inspect.getargspec
, you can get the default values (fourth item of the returned tuple = defaults
):
import inspect
def funA(x, a, b=1):
return a + b * x
# inspect.getargspec(funA) =>
# ArgSpec(args=['x', 'a', 'b'], varargs=None, keywords=None, defaults=(1,))
def funcB(x, a, b=inspect.getargspec(funA)[3][0]):
return funA(x, a, b)
OR (in Python 2.7+)
def funcB(x, a, b=inspect.getargspec(funA).defaults[0]):
return funA(x, a, b)
In Python 3.5+, it's recommend to use inspect.signature
instead:
def funcB(x, a, b=inspect.signature(funA).parameters['b'].default):
return funA(x, a, b)
Upvotes: 2
Reputation: 1279
You can also use:
def funA(x, a, b=1):
return a+b*x
def funB(x, a, b=None):
return funA(*filter(lambda o: o is not None, [x, a, b]))
Version which will not fail if x or a are None:
def funB(x, a, b=None):
return funA(*([x, a]+filter(lambda o: o is not None, [b])))
Upvotes: 0
Reputation: 114240
The way you did it is fine. Another way is for funB
to have the same defaults as funA
, so you can pass the same parameters right through. E.g., if you do def funB(x, a, b=1)
, then you can always call return funA(x, a, b)
just like that.
For simple cases, the above will work fine. For more complex cases, you may want to use *args
and **kwargs
(explained here and here). Specifically, you can pass in all your keyword arguments as a dictionary (conventionally called kwargs
). In this case, each function would set its own independent defaults, and you would just pass the whole dictionary through:
def funA(x, a, **kwargs):
b = kwargs.get("b", 1)
return a+b*x
def funB(x, a, **kwargs):
return funA(x, a, **kwargs)
If kwargs
is empty when passed to funB
(b
is not specified), it will be set to the default in funA
by the statement b = kwargs.get("b", 1)
. If b
is specified, it will be passed through as-is. Note that in funB
, you can access b
with its own, independent default value and still get the behavior you are looking for.
While this may seem like overkill for your example, extracting a couple of arguments at the beginning of a function is not a big deal if the function is complex enough. It also gives you a lot more flexibility (such as avoiding many of the common gotchas).
Upvotes: 3
Reputation: 2896
I would replace if b
with if b is not None
, so that if you pass b=0
(or any other "falsy" value) as argument to funB
it will be passed to funA
.
Apart from that it seems pretty pythonic to me: clear and explicit. (albeit maybe a bit useless, depending on what you're trying to do!)
A little more cryptic way that relies on calling funB
with the correct keyword arguments (e.g. funB(3, 2, b=4)
:
def funB(x, a, **kwargs):
return funA(x, a, **kwargs)
Upvotes: 16
Reputation: 4667
def funA(x, a, b=1):
return a+b*x
def funB(x, a, b=1):
return funA(x, a, b)
Make the default value of b=1
in funB()
and then pass it always to funA()
Upvotes: 7