Python -- optional arguments for a function - python

Say I want to build a function that either adds two numbers a and b , or subtracts them and adds a third number c and subtracts the fourth number d. I intend to specify which of the two operations is to be performed by an argument sum; if this is True, the first operation is performed, if False, then the second operations is performed. I would write this as:
def f(a, b, c, d, sum=True):
if sum: return a+b
else: return a-b+c-d
For example, f(1,2,3,4) returns 3, while f(1,2,3,4,sum=False) returns -2, as expected. Clearly, though, c and d only need to be defined when sum=False. How do I do this? I've tried setting c and d as *args, but I keep getting errors of the type "unsupported operand" or "positional argument follows keyword argument".

Use a default value of None for c and d:
def f(a, b, c=None, d=None, sum=True):
if sum:
return a+b
else:
return a-b+c-d
This also allows you to add error-checking logic to your function - check whether c and d are present when sum is False:
def f(a, b, c=None, d=None, sum=True):
if sum:
return a+b
else:
if None in (c, d):
raise TypeError('f requires c and d args with parameter sum=False')
return a-b+c-d

Others have given the way you can do it, but at a more fundamental level I'd ask why this is even the same method? Seems to me you have two different methods here, f_sum and f (or whatever), one takes two parameters and the other 4.
Or if it's a more complex operation and c and d are just additional parameters / attributes, you could default them to whatever the null value is e.g. for addition just default them to 0:
def f(a, b, c=0, d=0)
a+b-0+0 won't do anything so if these parameters are not provided the result will be identical to a+b without even needing a conditional or magical flag.
sorry missed that the second case was a - b and misread it as a +

This solves your problem:
def f(a, b, c=0, d=0, sum=True):
if sum: return a+b
else: return a-b+c-d

The answer to your question is:
def f(a, b, c=None, d=None, sum=True):
if sum: return a + b
else: return a - b + c - d
However, you could further simplify it to this:
def f(a, b, c=0, d=0):
return a - b + c - d
As the values of c and d depend on whether sum is true or false.

Related

Add a function using another function's parameter declaration

I am trying to add some customized logic outside of an existing function. Here are the example:
# existing function that I cannot change
def sum(a, b, c, d):
return a+b+c+d
# the function I want to build
def sumMultiply(a, b, c, d, multiplier):
return multiplier * sum(a, b, c, d)
This is a stupid example, but essentially I want to build a new function that takes all the parameter of the existing function and add a few new arguments.
The above solution is problematic when the existing function changes its definition. For example:
# in some updates the original function dropped one parameter
def sum(a, b, c):
return a+b+c
# the new function will give an error since there is no parameter "d"
def sumMultiply(a, b, c, d, multiplier):
return multiplier * sum(a, b, c, d) # error
How can I specify the new function so that I do not need to worry about changing the new function definition when the existing function definition changes?
One way would be to use arbitrary positional or keyword arguments:
def sumMultiply(multiplier, *numbers):
return multiplier * sum(*numbers)
def sumMultiply(multiplier, *args, **kwargs):
return multiplier * sum(*args, **kwargs)
However, if you see yourself passing around the same set of data around, consider making a parameter object. In your case, it can simply be a list:
def sum(numbers):
...
def sumMultiply(multiplier, numbers):
return multiplier * sum(numbers)
There are some additional downsides to using arbitrary arguments:
the arguments are implicit: you might need to dig through several layers to see what you actually need to provide
they don't play well with type annotations and other static analysers (e.g. PyCharm's refactorings)
I would create a decorator function
def create_fun_multiplier(fun, multiplier=1):
def multiplier_fun(*args):
return multiplier * fun(*args)
return multiplier_fun
def my_sum(a, b, c):
return a + b + c
sumMultiply = create_fun_multiplier(my_sum, multiplier=2)
print(sumMultiply(3, 4, 7))
I would look at using keyword args for this problem.
eg.
def sum(a, b, c):
return a + b + c
def sumMultiply(*args, multiplier=1):
return multiplier * sum(*args)

Function as an argument of another function

I'm learning this language hence I'm new with Python. The code is:
def add(a, b):
return a + b
def double_add(x, a, b):
return x(x(a, b), x(a, b))
a = 4
b = 5
print(double_add(add, a, b))
The add function is simple, it adds two numbers. The double_add function has three arguments. I understand what is happening (With some doubts). The result is 18. I can't understand how double_add uses add to function.
The question is, what is the connection between these two functions?
It would be helpful if tell me some examples of using a function as an argument of another function.
Thanks in advance.
In python language, functions (and methods) are first class objects. First Class objects are those objects, which can be handled uniformly.
So, you just pass a method as an argument.
Your method will return add(add(4, 5), add(4, 5)) which is add(9, 9) and it's equals to 18.
A function is an object just like any other in Python. So you can pass it as argument, assign attributes to it, and well maybe most importantely - call it. We can look at a simpler example to understand how passing a function works:
def add(a, b):
return a + b
def sub(a, b):
return a - b
def operate(func, a, b):
return func(a, b)
a = 4
b = 5
print(operate(add, a, b))
print(operate(sub, a, b))
operate(print, a, b)
And this prints out:
9
-1
4 5
That is because in each case, func is assigned with the respective function object passed as an argument, and then by doing func(a, b) it actually calls that function on the given arguments.
So what happens with your line:
return x(x(a, b), x(a, b))
is first both x(a, b) are evaluated as add(4, 5) which gives 9. And then the outer x(...) is evaluated as add(9, 9) which gives 18.
If you would add print(x) in the double_add function you would see that it would print <function add at 0x10dd12290>.
Therefore, the code of double_add is basically the same as if you would do following:
print(add(add(a,b), add(a,b))) # returns 18 in your case
Functions are objects in Python, just like anything else such as lists, strings.. and you can pass them same way you do with variables.
The function object add is passed as an argument to double_add, where it is locally referred to as x. x is then called on each, and then on the two return values from that.
def double_add(x, a, b):
return x(x(a, b), x(a, b))
Let's write it differently so it's easier to explain:
def double_add(x, a, b):
result1 = x(a, b)
result2 = x(a, b)
return x(result1, result2)
This means, take the function x, and apply it to the parameters a and b. x could be whatever function here.
print(double_add(add, a, b))
Then this means: call the double_add function, giving itaddas the first parameter. Sodouble_add`, would do:
result1 = add(a, b)
result2 = add(a, b)
return add(result1, result2)
This is a very simple example of what is called "dependency injection". What it means is that you are not explicitly defining an interaction between the two functions, instead you are defining that double_add should use some function, but it only knows what it is when the code is actually run. (At runtime you are injecting the depedency on a specific function, instead of hardcoding it in the function itself),
Try for example the following
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def double_add(x, a, b):
return x(x(a, b), x(a, b))
a = 4
b = 5
print(double_add(add, a, b))
print(double_add(subtract, a, b))
In other words, double_add has become a generic function that will execute whatever you give it twice and print the result

Python function pointer with different arguments

I have defined three functions.
def evaluate1(a, b):
pass
def evaluate2(a, b):
pass
def evaluate3(a, b, c):
pass
What I want to do use a pointer to record which evaluate function I will use depending on the test inputs. The logic is as shown follows:
def test(a, b, c, d):
# let evaluate_function records which evaluate function I will use
if c > 1:
evaluate_function = evaluate3 # not sure
else:
if d:
evaluate_function = evaluate1
else:
evaluate_function = evaluate2
# execute the evaluate function
evaluate_function(a, b, ?)
However, since evaluate3 has different arguments from evaluate1 and evaluate3. How should I do? Thanks!
You have come up with a good idea of using a 'function pointer' to select the function. But since you know which function you are selecting at the time, you could also bind up the params:
def test(a, b, c, d):
# let evaluate_function records which evaluate function I will use
if c > 1:
evaluate_function = evaluate3 # not sure
params = a,b,d
else:
if d:
evaluate_function = evaluate1
params = a,b
else:
evaluate_function = evaluate2
params = a,c
# execute the evaluate function
evaluate_function(*params)
I'll leave it to you to properly select the params.
Why not just call the evaluate functions directly instead of assigning them to a function as so. Makes it more readable
def evaluate1(a, b):
print('evaluate1')
def evaluate2(a, b):
print('evaluate2')
def evaluate3(a, b, c):
print('evaluate3')
def test(a, b, c=None, d=None):
# let evaluate_function records which evaluate function I will use
if c and c > 1:
evaluate3(a, b, c)
else:
if d:
evaluate1(a, b)
else:
evaluate2(a, c)
test(1,2,c=0.1,d=1)
#evaluate1
test(1,2)
#evaluate2
test(1,2,3)
#evaluate3

Default values for iterable unpacking

I've often been frustrated by the lack of flexibility in Python's iterable unpacking.
Take the following example:
a, b = range(2)
Works fine. a contains 0 and b contains 1, just as expected. Now let's try this:
a, b = range(1)
Now, we get a ValueError:
ValueError: not enough values to unpack (expected 2, got 1)
Not ideal, when the desired result was 0 in a, and None in b.
There are a number of hacks to get around this. The most elegant I've seen is this:
a, *b = function_with_variable_number_of_return_values()
b = b[0] if b else None
Not pretty, and could be confusing to Python newcomers.
So what's the most Pythonic way to do this? Store the return value in a variable and use an if block? The *varname hack? Something else?
As mentioned in the comments, the best way to do this is to simply have your function return a constant number of values and if your use case is actually more complicated (like argument parsing), use a library for it.
However, your question explicitly asked for a Pythonic way of handling functions that return a variable number of arguments and I believe it can be cleanly accomplished with decorators. They're not super common and most people tend to use them more than create them so here's a down-to-earth tutorial on creating decorators to learn more about them.
Below is a decorated function that does what you're looking for. The function returns an iterator with a variable number of arguments and it is padded up to a certain length to better accommodate iterator unpacking.
def variable_return(max_values, default=None):
# This decorator is somewhat more complicated because the decorator
# itself needs to take arguments.
def decorator(f):
def wrapper(*args, **kwargs):
actual_values = f(*args, **kwargs)
try:
# This will fail if `actual_values` is a single value.
# Such as a single integer or just `None`.
actual_values = list(actual_values)
except:
actual_values = [actual_values]
extra = [default] * (max_values - len(actual_values))
actual_values.extend(extra)
return actual_values
return wrapper
return decorator
#variable_return(max_values=3)
# This would be a function that actually does something.
# It should not return more values than `max_values`.
def ret_n(n):
return list(range(n))
a, b, c = ret_n(1)
print(a, b, c)
a, b, c = ret_n(2)
print(a, b, c)
a, b, c = ret_n(3)
print(a, b, c)
Which outputs what you're looking for:
0 None None
0 1 None
0 1 2
The decorator basically takes the decorated function and returns its output along with enough extra values to fill in max_values. The caller can then assume that the function always returns exactly max_values number of arguments and can use fancy unpacking like normal.
Here's an alternative version of the decorator solution by #supersam654, using iterators rather than lists for efficiency:
def variable_return(max_values, default=None):
def decorator(f):
def wrapper(*args, **kwargs):
actual_values = f(*args, **kwargs)
try:
for count, value in enumerate(actual_values, 1):
yield value
except TypeError:
count = 1
yield actual_values
yield from [default] * (max_values - count)
return wrapper
return decorator
It's used in the same way:
#variable_return(3)
def ret_n(n):
return tuple(range(n))
a, b, c = ret_n(2)
This could also be used with non-user-defined functions like so:
a, b, c = variable_return(3)(range)(2)
Shortest known to me version (thanks to #KellyBundy in comments below):
a, b, c, d, e, *_ = *my_list_or_iterable, *[None]*5
Obviously it's possible to use other default value than None if necessary.
Also there is one nice feature in Python 3.10 which comes handy here when we know upfront possible numbers of arguments - like when unpacking sys.argv
Previous method:
import sys.argv
_, x, y, z, *_ = *sys.argv, *[None]*3
New method:
import sys
match sys.argv[1:]: #slice needed to drop first value of sys.argv
case [x]:
print(f'x={x}')
case [x,y]:
print(f'x={x}, y={y}')
case [x,y,z]:
print(f'x={x}, y={y}, z={z}')
case _:
print('No arguments')

Call another function and optionally keep default arguments

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?
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)
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()
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).
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)
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"
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])))

Categories