Call function without optional arguments if they are None
Question:
There’s a function which takes optional arguments.
def alpha(p1="foo", p2="bar"):
print('{0},{1}'.format(p1, p2))
Let me iterate over what happens when we use that function in different ways:
>>> alpha()
foo,bar
>>> alpha("FOO")
FOO,bar
>>> alpha(p2="BAR")
foo,BAR
>>> alpha(p1="FOO", p2=None)
FOO,None
Now consider the case where I want to call it like alpha("FOO", myp2)
and myp2
will either contain a value to be passed, or be None
. But even though the function handles p2=None
, I want it to use its default value "bar"
instead.
Maybe that’s worded confusingly, so let me reword that:
If myp2 is None
, call alpha("FOO")
. Else, call alpha("FOO", myp2)
.
The distinction is relevant because alpha("FOO", None)
has a different result than alpha("FOO")
.
How can I concisely (but readably) make this distinction?
One possibility would usually be to check for None within alpha
, which would be encouraged because that would make the code safer. But assume that alpha
is used in other places where it is actually supposed to handle None
as it does.
I’d like to handle that on the caller-side.
One possibility is to do a case distinction:
if myp2 is None:
alpha("FOO")
else:
alpha("FOO", myp2)
But that can quickly become much code when there are multiple such arguments. (exponentially, 2^n)
Another possibility is to simply do alpha("FOO", myp2 or "bar")
, but that requires us to know the default value. Usually, I’d probably go with this approach, but I might later change the default values for alpha
and this call would then need to be updated manually in order to still call it with the (new) default value.
I am using python 3.4 but it would be best if your answers can provide a good way that works in any python version.
The question is technically finished here, but I reword some requirement again, since the first answer did gloss over that:
I want the behaviour of alpha
with its default values "foo", "bar"
preserved in general, so it is (probably) not an option to change alpha
itself.
In yet again other words, assume that alpha
is being used somewhere else as alpha("FOO", None)
where the output FOO,None
is expected behaviour.
Answers:
Pass the arguments as kwargs from a dictionary, from which you filter out the None
values:
kwargs = dict(p1='FOO', p2=None)
alpha(**{k: v for k, v in kwargs.items() if v is not None})
Unfortunately, there’s no way to do what you want. Even widely adopted python libraries/frameworks use your first approach. It’s an extra line of code, but it is quite readable.
Do not use the alpha("FOO", myp2 or "bar")
approach, because, as you mention yourself, it creates a terrible kind of coupling, since it requires the caller to know details about the function.
Regarding work-arounds: you could make a decorator for you function (using the inspect
module), which checks the arguments passed to it. If one of them is None
, it replaces the value with its own default value.
You could inspect the default values via alpha.__defaults__
and then use them instead of None
. That way you circumvent the hard-coding of default values:
>>> args = [None]
>>> alpha('FOO', *[x if x is not None else y for x, y in zip(args, alpha.__defaults__[1:])])
although ** is definitely a language feature, it’s surely not created for solving this particular problem. Your suggestion works, so does mine. Which one works better depends on the rest of the OP’s code. However, there is still no way to write f(x or dont_pass_it_at_all)
– blue_note
Thanks to your great answers, I thought I’d try to do just that:
# gen.py
def callWithNonNoneArgs(f, *args, **kwargs):
kwargsNotNone = {k: v for k, v in kwargs.items() if v is not None}
return f(*args, **kwargsNotNone)
# python interpreter
>>> import gen
>>> def alpha(p1="foo", p2="bar"):
... print('{0},{1}'.format(p1,p2))
...
>>> gen.callWithNonNoneArgs(alpha, p1="FOO", p2=None)
FOO,bar
>>> def beta(ree, p1="foo", p2="bar"):
... print('{0},{1},{2}'.format(ree,p1,p2))
...
>>> beta('hello', p2="world")
hello,foo,world
>>> beta('hello', p2=None)
hello,foo,None
>>> gen.callWithNonNoneArgs(beta, 'hello', p2=None)
hello,foo,bar
This is probably not perfect, but it seems to work: It’s a function that you can call with another function and it’s arguments, and it applies deceze’s answer to filter out the arguments that are None
.
But assume that alpha is used in other places where it is actually supposed to handle None as it does.
To respond to this concern, I have been known to have a None
-like value which isn’t actually None
for this exact purpose.
_novalue = object()
def alpha(p1=_novalue, p2=_novalue):
if p1 is _novalue:
p1 = "foo"
if p2 is _novalue:
p2 = "bar"
print('{0},{1}'.format(p1, p2))
Now the arguments are still optional, so you can neglect to pass either of them. And the function handles None
correctly. If you ever want to explicitly not pass an argument, you can pass _novalue
.
>>> alpha(p1="FOO", p2=None)
FOO,None
>>> alpha(p1="FOO")
FOO,bar
>>> alpha(p1="FOO", p2=_novalue)
FOO,bar
and since _novalue
is a special made-up value created for this express purpose, anyone who passes _novalue
is certainly intending the “default argument” behavior, as opposed to someone who passes None
who might intend that the value be interpreted as literal None
.
I’m surprised nobody brought this up
def f(p1="foo", p2=None):
p2 = "bar" if p2 is None else p2
print(p1+p2)
You assign None to p2 as standart (or don’t, but this way you have the true standart at one point in your code) and use an inline if. Imo the most pythonic answer. Another thing that comes to mind is using a wrapper, but that would be way less readable.
EDIT:
What I’d probably do is use a dummy as standart value and check for that. So something like this:
class dummy():
pass
def alpha(p1="foo", p2=dummy()):
if isinstance(p2, dummy):
p2 = "bar"
print("{0},{1}".format(p1, p2))
alpha()
alpha("a","b")
alpha(p2=None)
produces:
foo,bar
a,b
foo,None
Not a direct answer, but I think this is worth considering:
See if you can break your function into several functions, neither of which has any default arguments. Factor any shared functionality out to a function you designate as internal.
def alpha():
_omega('foo', 'bar')
def beta(p1):
_omega(p1, 'bar')
def _omega(p1, p2):
print('{0},{1}'.format(p1, p2))
This works well when the extra arguments trigger “extra” functionality, as it may allow you to give the functions more descriptive names.
Functions with boolean arguments with True and/or False defaults frequently benefit from this type of approach.
I had the same problem when calling some Swagger generated client code, which I couldn’t modify, where None
could end up in the query string if I didn’t clean up the arguments before calling the generated methods. I ended up creating a simple helper function:
def defined_kwargs(**kwargs):
return {k: v for k, v in kwargs.items() if v is not None}
>>> alpha(**defined_kwargs(p1="FOO", p2=None))
FOO,bar
It keeps things quite readable for more complex invocations:
def beta(a, b, p1="foo", p2="bar"):
print('{0},{1},{2},{3}'.format(a, b, p1, p2,))
p1_value = "foo"
p2_value = None
>>> beta("hello",
"world",
**defined_kwargs(
p1=p1_value,
p2=p2_value))
hello,world,FOO,bar
Another possibility is to simply do alpha("FOO", myp2 or "bar")
, but that requires us to know the default value. Usually, I’d probably go with this approach, but I might later change the default values for alpha
and this call would then need to be updated manually in order to still call it with the (new) default value.
Just create a constant:
P2_DEFAULT = "bar"
def alpha(p1="foo", p2=P2_DEFAULT):
print('{0},{1}'.format(p1, p2))
and call the function:
alpha("FOO", myp2 or P2_DEFAULT)
If default values for alpha
will be changed, we have to change only one constant.
Be careful with logical or
for some cases, see https://stackoverflow.com/a/4978745/3605259
One more (better) use case
For example, we have some config (dictionary). But some values are not present:
config = {'name': 'Johnny', 'age': '33'}
work_type = config.get('work_type', P2_DEFAULT)
alpha("FOO", work_type)
So we use method get(key, default_value)
of dict, which will return default_value
if our config (dict) does not contain such key.
There’s a function which takes optional arguments.
def alpha(p1="foo", p2="bar"):
print('{0},{1}'.format(p1, p2))
Let me iterate over what happens when we use that function in different ways:
>>> alpha()
foo,bar
>>> alpha("FOO")
FOO,bar
>>> alpha(p2="BAR")
foo,BAR
>>> alpha(p1="FOO", p2=None)
FOO,None
Now consider the case where I want to call it like alpha("FOO", myp2)
and myp2
will either contain a value to be passed, or be None
. But even though the function handles p2=None
, I want it to use its default value "bar"
instead.
Maybe that’s worded confusingly, so let me reword that:
If
myp2 is None
, callalpha("FOO")
. Else, callalpha("FOO", myp2)
.
The distinction is relevant because alpha("FOO", None)
has a different result than alpha("FOO")
.
How can I concisely (but readably) make this distinction?
One possibility would usually be to check for None within alpha
, which would be encouraged because that would make the code safer. But assume that alpha
is used in other places where it is actually supposed to handle None
as it does.
I’d like to handle that on the caller-side.
One possibility is to do a case distinction:
if myp2 is None:
alpha("FOO")
else:
alpha("FOO", myp2)
But that can quickly become much code when there are multiple such arguments. (exponentially, 2^n)
Another possibility is to simply do alpha("FOO", myp2 or "bar")
, but that requires us to know the default value. Usually, I’d probably go with this approach, but I might later change the default values for alpha
and this call would then need to be updated manually in order to still call it with the (new) default value.
I am using python 3.4 but it would be best if your answers can provide a good way that works in any python version.
The question is technically finished here, but I reword some requirement again, since the first answer did gloss over that:
I want the behaviour of alpha
with its default values "foo", "bar"
preserved in general, so it is (probably) not an option to change alpha
itself.
In yet again other words, assume that alpha
is being used somewhere else as alpha("FOO", None)
where the output FOO,None
is expected behaviour.
Pass the arguments as kwargs from a dictionary, from which you filter out the None
values:
kwargs = dict(p1='FOO', p2=None)
alpha(**{k: v for k, v in kwargs.items() if v is not None})
Unfortunately, there’s no way to do what you want. Even widely adopted python libraries/frameworks use your first approach. It’s an extra line of code, but it is quite readable.
Do not use the alpha("FOO", myp2 or "bar")
approach, because, as you mention yourself, it creates a terrible kind of coupling, since it requires the caller to know details about the function.
Regarding work-arounds: you could make a decorator for you function (using the inspect
module), which checks the arguments passed to it. If one of them is None
, it replaces the value with its own default value.
You could inspect the default values via alpha.__defaults__
and then use them instead of None
. That way you circumvent the hard-coding of default values:
>>> args = [None]
>>> alpha('FOO', *[x if x is not None else y for x, y in zip(args, alpha.__defaults__[1:])])
although ** is definitely a language feature, it’s surely not created for solving this particular problem. Your suggestion works, so does mine. Which one works better depends on the rest of the OP’s code. However, there is still no way to write
f(x or dont_pass_it_at_all)
– blue_note
Thanks to your great answers, I thought I’d try to do just that:
# gen.py
def callWithNonNoneArgs(f, *args, **kwargs):
kwargsNotNone = {k: v for k, v in kwargs.items() if v is not None}
return f(*args, **kwargsNotNone)
# python interpreter
>>> import gen
>>> def alpha(p1="foo", p2="bar"):
... print('{0},{1}'.format(p1,p2))
...
>>> gen.callWithNonNoneArgs(alpha, p1="FOO", p2=None)
FOO,bar
>>> def beta(ree, p1="foo", p2="bar"):
... print('{0},{1},{2}'.format(ree,p1,p2))
...
>>> beta('hello', p2="world")
hello,foo,world
>>> beta('hello', p2=None)
hello,foo,None
>>> gen.callWithNonNoneArgs(beta, 'hello', p2=None)
hello,foo,bar
This is probably not perfect, but it seems to work: It’s a function that you can call with another function and it’s arguments, and it applies deceze’s answer to filter out the arguments that are None
.
But assume that alpha is used in other places where it is actually supposed to handle None as it does.
To respond to this concern, I have been known to have a None
-like value which isn’t actually None
for this exact purpose.
_novalue = object()
def alpha(p1=_novalue, p2=_novalue):
if p1 is _novalue:
p1 = "foo"
if p2 is _novalue:
p2 = "bar"
print('{0},{1}'.format(p1, p2))
Now the arguments are still optional, so you can neglect to pass either of them. And the function handles None
correctly. If you ever want to explicitly not pass an argument, you can pass _novalue
.
>>> alpha(p1="FOO", p2=None)
FOO,None
>>> alpha(p1="FOO")
FOO,bar
>>> alpha(p1="FOO", p2=_novalue)
FOO,bar
and since _novalue
is a special made-up value created for this express purpose, anyone who passes _novalue
is certainly intending the “default argument” behavior, as opposed to someone who passes None
who might intend that the value be interpreted as literal None
.
I’m surprised nobody brought this up
def f(p1="foo", p2=None):
p2 = "bar" if p2 is None else p2
print(p1+p2)
You assign None to p2 as standart (or don’t, but this way you have the true standart at one point in your code) and use an inline if. Imo the most pythonic answer. Another thing that comes to mind is using a wrapper, but that would be way less readable.
EDIT:
What I’d probably do is use a dummy as standart value and check for that. So something like this:
class dummy():
pass
def alpha(p1="foo", p2=dummy()):
if isinstance(p2, dummy):
p2 = "bar"
print("{0},{1}".format(p1, p2))
alpha()
alpha("a","b")
alpha(p2=None)
produces:
foo,bar
a,b
foo,None
Not a direct answer, but I think this is worth considering:
See if you can break your function into several functions, neither of which has any default arguments. Factor any shared functionality out to a function you designate as internal.
def alpha():
_omega('foo', 'bar')
def beta(p1):
_omega(p1, 'bar')
def _omega(p1, p2):
print('{0},{1}'.format(p1, p2))
This works well when the extra arguments trigger “extra” functionality, as it may allow you to give the functions more descriptive names.
Functions with boolean arguments with True and/or False defaults frequently benefit from this type of approach.
I had the same problem when calling some Swagger generated client code, which I couldn’t modify, where None
could end up in the query string if I didn’t clean up the arguments before calling the generated methods. I ended up creating a simple helper function:
def defined_kwargs(**kwargs):
return {k: v for k, v in kwargs.items() if v is not None}
>>> alpha(**defined_kwargs(p1="FOO", p2=None))
FOO,bar
It keeps things quite readable for more complex invocations:
def beta(a, b, p1="foo", p2="bar"):
print('{0},{1},{2},{3}'.format(a, b, p1, p2,))
p1_value = "foo"
p2_value = None
>>> beta("hello",
"world",
**defined_kwargs(
p1=p1_value,
p2=p2_value))
hello,world,FOO,bar
Another possibility is to simply do
alpha("FOO", myp2 or "bar")
, but that requires us to know the default value. Usually, I’d probably go with this approach, but I might later change the default values foralpha
and this call would then need to be updated manually in order to still call it with the (new) default value.
Just create a constant:
P2_DEFAULT = "bar"
def alpha(p1="foo", p2=P2_DEFAULT):
print('{0},{1}'.format(p1, p2))
and call the function:
alpha("FOO", myp2 or P2_DEFAULT)
If default values for alpha
will be changed, we have to change only one constant.
Be careful with logical or
for some cases, see https://stackoverflow.com/a/4978745/3605259
One more (better) use case
For example, we have some config (dictionary). But some values are not present:
config = {'name': 'Johnny', 'age': '33'}
work_type = config.get('work_type', P2_DEFAULT)
alpha("FOO", work_type)
So we use method get(key, default_value)
of dict, which will return default_value
if our config (dict) does not contain such key.