I have a function with two optional parameters:
def func(a=0, b=10):
return a+b
Somewhere else in my code I am doing some conditional argument passing like:
if a and b:
return func(a, b)
elif a:
return func(a)
elif b:
return func(b=b)
else:
return func()
Is there anyway to simplify code in this pattern?
EDIT:
Let's assume that I'm not allowed to implement default parameter logic inside func.
I may have several functions like func: func1, func2 and func3 would all contain the
a = a or 0
b = b or 10
statements.
But I'm invoking these series of functions to eliminate duplication. (using a decorator)
If you don't want to change anything in func then the sensible option would be passing a dict of arguments to the function:
>>> def func(a=0,b=10):
... return a+b
...
>>> args = {'a':15,'b':15}
>>> func(**args)
30
>>> args={'a':15}
>>> func(**args)
25
>>> args={'b':6}
>>> func(**args)
6
>>> args = {}
>>> func(**args)
10
or just:
>>>func(**{'a':7})
17
You can add a decorator that would eliminate None arguments:
def skip_nones(fun):
def _(*args, **kwargs):
for a, v in zip(fun.__code__.co_varnames, args):
if v is not None:
kwargs[a] = v
return fun(**kwargs)
return _
#skip_nones
def func(a=10, b=20):
print a, b
func(None, None) # 10 20
func(11, None) # 11 20
func(None, 33) # 10 33
Going by the now-deleted comments to the question that the check is meant to be for the variables being None rather than being falsey, change func so that it handles the arguments being None:
def func(a=None, b=None):
if a is None:
a = 0
if b is None:
b = 10
And then just call func(a, b) every time.
to solve your specific question I would do:
args = {'a' : a, 'b' : b}
for varName, varVal in args.items():
if not varVal:
del args[varName]
f(**args)
But the most pythonic way would be to use None as the default value in your function:
f(a=None, b=None):
a = 10 if a is None else a
...
and just call f(a, b)
By default, all methods in Python take variable arguments.
When you define an argument in the signature of the method, you explicity make it required. In your snippet, what you are doing is giving it a default value - not making them optional.
Consider the following:
>>> def func(a,b):
... a = a if a else 0
... b = b if b else 10
... return a+b
...
>>> a = b = None
>>> func(a,b)
10
>>> a = 5
>>> b = 2
>>> func(a,b)
7
>>> func()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func() takes exactly 2 arguments (0 given)
In this snippet, both a and b are required, since I didn't define any default values. They are not optional.
However in your snippet, because you have given them defaults in the method signature they look like they are optional, but in fact they just have defaults assigned to them.
>>> def func(a=0,b=10):
... return a+b
...
>>> func()
10
You can confirm this by checking the argument list inside the body of the method:
>>> def func(a=b,b=10):
... print locals().keys()
...
>>> func()
['a', 'b']
One way to have your function accept any number of arguments (in other words, making them all optional):
>>> def func(*args,**kwargs):
... print len(args),args
... print len(kwargs),kwargs
...
>>> func("hello","world",a=5,b=10)
2 ('hello', 'world')
2 {'a': 5, 'b': 10}
>>> func()
0 ()
0 {}
>>> func(1,2)
2 (1, 2)
0 {}
>>> func(a)
1 (5,)
0 {}
>>> func(foo="hello")
0 ()
1 {'foo': 'hello'}
Why not pass that logic to the function?
def func(a, b):
a = a or 0
b = b or 10
return a + b
To answer your literal question:
func(a or 0, b or 10)
Edit:
See comment why this doesn't answer the question.
You can use the ternary if-then operator to pass conditional arguements into functions
https://www.geeksforgeeks.org/ternary-operator-in-python/
For example, if you want to check if a key actually exists before passing it you can do something like:
def func(arg):
print(arg)
kwargs = {'name':'Adam')
func( kwargs['name'] if 'name' in kwargs.keys() else '' ) #Expected Output: 'Adam'
func( kwargs['notakey'] if 'notakey' in kwargs.keys() else '' ) #Expected Output: ''
This might work:
def f(**kwargs):
a = get(kwargs, 0)
b = get(kwargs, 10)
return a + b
Related
Given a simple function:
def A(a = 1, b = 2):
return a+b
I want to write another function to change the default parameter value, for either a or b. And user can specify the parameter to change by setting var = a or var = b. For example:
def B(var = 'a', new_value = 10):
temp_dict = {var:new_value}
ans = A(var)
return ans
or
def B(var = 'a', new_value = 10):
ans = A(var = new_value)
return ans
In function def B(), after setting var = a and var = new_value = 10, I expect A(var = new_value) to achieve the same effect as A(a = 10). Do you know the correct way to write function def B()?
Thanks.
You are almost there. From your B() function, while making the call to A(), you need to unpack the temp_dict and pass it as an argument to A(). See below:
>>> def A(a = 1, b = 2):
... return a+b
...
>>> def B(var = 'a', new_value = 10):
... temp_dict = {var:new_value}
... ans = A(**temp_dict)
# ^ unpack the dict, and pass it as an argument
... return ans
...
>>> B()
12
For more details on how this ** works with a dict, please take a look at:
Python: *args and **kwargs?
What does ** (double star/asterisk) and * (star/asterisk) do for parameters?
I took the liberty of interpreting a bit what the OP said he wanted, i.e. change the default parameter value, for either a or b. So what I did was to return a transformed function A with either the a or b defaults changed via a partial:
from functools import partial
def B3(var ="a", new_value=10):
return partial(A, **{var:new_value})
sample outputs:
(Pdb) f = B3("a",10)
(Pdb) f()
12
(Pdb) f = B3("b",10)
(Pdb) f()
11
(Pdb) f(a=10)
20
(Pdb) f(b=13)
14
(Pdb) f(a=5,b=5)
10
That is different from the 2nd half of the request however, that of having something based on B(var="a",new_value=10) as function signature.
The only thing is, it chokes happily if you don't use keyword parameters:
(Pdb) f(7,7)
*** TypeError: A() got multiple values for keyword argument 'b'
I would like to define a Python function in which a parameter is external if it is declared when the function is called and it is internal if it is not mentioned into the function calling.
An easy example will clarify my issue:
def func(a, b):
c = 4
try:
b
except:
b= c
return a + b
Now I want something in which I can write
func(2, 1)
and provide me the result (2 + 1 = 3), and I can even write
func(2)
and the function will generate a solution that is c + a = 6.
How can I do that?
You are describing a default argument. Python does that for you (see the docs):
def func(a, b=4):
return a + b
Alternatively, if more logic is involved, you might want to consider:
def func(a, b=None):
if b is None:
try:
b = fancy_function()
except FancyException:
b = 4
return a + b
The same holds for mutable default values:
def func(a, b=None):
if b is None:
b = []
b.append(a)
return b
Simply write
def func(a, b=4):
return a + b
>>> func(2)
6
>>> func(2, 1)
3
If the default value of b is not known beforehand, you can do the following:
def func(a, b=None):
if b is None:
b = some_function_that_generates_b()
return a + b
How can I make a Python function default value be evaluated each time the function is called?
Take this dummy code:
b=0
def a():
global b
return b
def c(d=a()):
return d
What I would expect as output:
>>> c()
0
>>> b=1
>>> a()
1
>>> c()
1
What I actually get:
>>> c()
0
>>> b=1
>>> a()
1
>>> c()
0
One more solution, in closer resemblance to your original answer.
b = 0
def a():
return b
def c(d=a): # When it's a parameter, the call will be evaluated and its return
# value will be used. Instead, just use the function name, because
return d() # within the scope of the function, the call will be evaluated every time.
When a function name is paired with the parentheses and its parameters, like f(x), it is assumed your intention is to call it at that time
d=a() is evaluated at start of program when function c is defined (ie a() gets called while it returns 0 ...)
def c(d=None):
if d == None: d=a()
return d
will cause it to be evaluated at the time you want
The problem here is, as you probably already know, that the d=a() (default argument assignement) is evaluated when the function is defined.
To change that, it is pretty common to use eg. None as default argument and evaluate it in the body of the function:
b=0
def a():
global b
return b
def c(d=None):
if d is None:
d = a()
return d
I'll give a slight variation on the above:
b = 0
def a():
# unless you are writing changes to b, you do not have to make it a global
return b
def c(d=None, get_d=a):
if d is None:
d = get_d()
return d
def func(a, b = 100):
return a + b
func(a)
func(a,b = 100)
Is there any way to tell when func is called, b value 100 is taken from the default or keyword parameter?
No, not as you've written your code. However, you can do:
def func(a, b=None):
if b is None:
b = 100
return a + b
An anonymous object is the way to go to cover all possible cases.
def foo(a, b=object()):
if b is foo.func_defaults[0]:
# no value was passed
# do whatever
It's not really func's business to know. But you can default to None instead.
def func(a, b=None):
if b is None:
# b = default b
b = 100
return a + b
You can use an object as the default value, e.g.
dummy = object()
def func(a, b=dummy):
used_default = b is dummy
if used_default:
b = 100
print used_default
func(0) # prints True
func(0, None) # prints False
func(0, object()) # prints False
If your intent is to check if b matches a default value exactly, then don't use a default value! If you absolutely have to leave b optional, pass a special value (perhaps -1) to denote an unusual case, and be sure to note this in your __doc__. Just make sure that you're not able to reach the exceptional value in any day-to-day use, and that the exceptions written to contain that logic do not modify sensitive areas of your code.
An alternative to the other suggestions is to use a kwargs parameter and then do something like this:
def func(a, **kwargs):
if 'b' in kwargs:
print 'b was passed in'
b = kwargs['b']
else:
print 'b was not passed in'
b = 100
return a + b
a = 50
func(a)
func(a, b = 100)
Output:
b was not passed in
150
b was passed in
150
Because kwargs is a dictionary containing all non-optional parameters you can examine this dictionary to determine what was/wasn't passed.
You can make the lookup of b more efficient rather then looking up kwargs twice if needed.
In Perl 5.10, I can say:
sub foo () {
state $x = 1;
say $x++;
}
foo();
foo();
foo();
...and it will print out:
1
2
3
Does Python have something like this?
A class may be a better fit here (and is usually a better fit for anything involving "state"):
class Stateful(object):
def __init__(self):
self.state_var = 0
def __call__(self):
self.state_var = self.state_var + 1
print self.state_var
foo = Stateful()
foo()
foo()
The closest parallel is probably to attach values to the function itself.
def foo():
foo.bar = foo.bar + 1
foo.bar = 0
foo()
foo()
foo()
print foo.bar # prints 3
Python has generators which do something similar:
What does the "yield" keyword do in Python?
Not sure if this is what you're looking for, but python has generator functions that don't return a value per se, but a generator object that generates a new value everytime
def gen():
x = 10
while True:
yield x
x += 1
usage:
>>> a = gen()
>>> a.next()
10
>>> a.next()
11
>>> a.next()
12
>>> a.next()
13
>>>
look here for more explanation on yield:
What does the "yield" keyword do in Python?
Here's one way to implement a closure in python:
def outer():
a = [4]
def inner():
print a[0]
a[0] = a[0] + 1
return inner
fn = outer()
fn() # => 4
fn() # => 5
fn() # => 6
I borrowed this example verbatim from a python mailing list post.
Not that I'm recommending this, but just for fun:
def foo(var=[1]):
print var[0]
var[0] += 1
This works because of the way mutable default arguments work in Python.
Yes, though you have to declare your global variable first before it is encountered in foo:
x = 0
def foo():
global x
x += 1
print x
foo()
foo()
foo()
EDIT: In response to the comment, it's true that python has no static variables scoped within a function. Note that x in this example is only exposed as global to the rest of the module. For example, say the code above is in test.py. Now suppose you write the following module:
from test import foo
x = 100
foo()
foo()
The output will be only 1 and 2, not 101 and 102.
You could also use something like
def static_num2():
k = 0
while True:
k += 1
yield k
static = static_num2().next
for i in range(0,10) :
print static()
to avoid a global var. Lifted from this link about the same question.
The preferable way is to use class or generator (yield).
For the sake of completeness here's a variant w/ closure in Python 3.x:
>>> def make_foo():
... x = 1
... def foo():
... nonlocal x
... print(x)
... x += 1
... return foo
...
>>> foo = make_foo()
>>> foo()
1
>>> foo()
2
>>> foo()
3
>>> def foo():
x = 1
while True:
yield x
x += 1
>>> z = iter(foo())
>>> next(z)
1
>>> next(z)
2
>>> next(z)
3
Here's another dirty cheap way to do it, it's a variation on Tryiptich's answer, but using decorators
def static_var( name, value ):
def dec( function ):
setattr( function, name, value )
return function
return dec
#static_var( 'counter', 0 )
def counting_function():
counting_function.counter = counting_function.counter + 1
print counting_function.counter
"""
>>> counting_function()
1
>>> counting_function()
2
>>> counting_function()
3
>>> counting_function()
4
>>> counting_function()
5
>>>
"""