I have a general question as well as a specific use case.
Optional parameters are easy enough: def func(a, b, c=None): ... and then anywhere c might be used in the body just write if c: first, or something along those lines. But what about when a certain combination of parameters is required? The general case is to consider any arbitrary situation of which exact parameters exist or not. For a function def func(a, b, c=None, d=None, e=None, f=None): ... this would include silly things like: provide c and d but not e and f, or provide e only, or provide at least 3 of c, d, e, and f. But my use case doesn't require such generality.
For def func(a, b, c=None, d=None): ..., I want EXACTLY ONE OF c and d to be provided.
Solutions I've thought of include:
- in the body, manually check how many of c and d are not None, and if it's not exactly 1, return an error saying exactly 1 needs to be specified
ex.
def func(a, b, c=None, d=None):
how_many_provided = len([arg for arg in [c, d] if arg]) # count the non-None optional args
if not how_many_provided == 1:
return "Hey, provide exactly 1 of 'c' and 'd'"
if c:
# stuff to do if c is provided
elif d:
# stuff to do if d is provided
- change the function to be def func(a, b, e, f): ... where e represents either c or d and f indicates which one of those e represents.
ex.
def func(a, b, e, f):
if f == 'c':
# stuff to do if c is provided, with e as c
if f == 'd':
# stuff to do if d is provided, with e as d
These would work, but what is the standard/accepted/pythonic way of doing this?
I would say the easiest way for your user in your simple case is to refactor to separate functions. Each function does the different work as described and then a common one e.g. for your last case
def funcC(a, b, c):
# stuff to do if c is provided, with e as c
common_func(a,b,c, None)
def funcD(a, b, d):
# stuff to do if d is provided, with e as d
common_func(a,b,None, d)
The user then knows what parameters matter and only the valid possible combinations can be used, the user does not have to guess or have a chance to call them incorrectly. You as providing the function can provide whatever is needed for the parameter the caller does not supply.
There are longer explanations of these found by googling for "flag parameters" e.g. Martin Fowler Stack Overflow these tend to mention Boolean arguments but this in effect the same thing a different code path depending on a parameter which has no other effect.
Another phrase to look for is "control coupling"
You could just use the keyword args dict:
def func(a, b, **kwargs):
valid_args = len(kwargs) == 1 and ('c' in kwargs or 'd' in kwargs)
if not valid_args:
return "Hey, provide exactly 1 of 'c' and 'd'"
if 'c' in kwargs:
# stuff to do if c is provided
elif 'd' in kwargs:
# stuff to do if d is provided
Here is another one, which will allow the arguments be specified, and differentiates between c=None and c not given, while still providing the argument names explicitly:
undefined = object()
def func(a, b, c=undefined, d=undefined):
if (c is undefined) ^ (d is undefined):
raise TypeError("Hey, provide exactly 1 of 'c' and 'd'")
...
On Python 3, keyword only arguments make it even nicer, making sure that the caller explicitly specifies c or d:
def func(a, b, *, c=undefined, d=undefined):
if (c is undefined) ^ (d is undefined):
raise TypeError("Hey, provide exactly 1 of 'c' and 'd'")
Related
I have a function-object f, which takes 4 numeric inputs and outputs two numbers. Maybe
def f(a, b, c, d):
return a+b, c+d
or maybe
def f(a, b, c, d):
return a*c, d*c
To be clear, I don't actually know what f is, I just have it as an object.
I would like to create a new function-object, h, such that h(a,b,c,d)=x*c+y where (x,y)=f(a,b,c,d). The trouble is, I have no direct access to c, only to f.
def make_h(f):
???
return h
assert( make_h(f)(a,b,c,d) == f(a,b,c,d)[0]*c+f(a,b,c,d)[1])
Is it possible to do this in python? I have tried searching and reading some documentation, but have not found an answer (yet?).
EDIT: There is a simple answer (given below) when the signature of f is fixed. Suppose I had to do this to different functions, some with inputs (a, b, c, d), some with inputs (l, m, c), and maybe some with inputs (c, r). Would it still be possible to do what I want?
This example is strongly related to the concept of a decorator. My solution is the following:
def make_h(f):
def h(a, b, c, d):
x, y = f(a,b,c,d)
return x * c + y
return h
UPDATE. In case f has any number of arguments, we can use args, and kwargs. While it is a bad practice, if we know that one of kwargs is c, we could use the following code:
def make_h(f):
def h(*args, **kwargs):
x, y = f(*args, **kwargs)
return x * kwargs["c"] + y
return h
I have a part of code in python, which calls dynamically different functions, where I always want to pass 3 different arguments. However, these functions, might not always need to use those 3 different arguments.
Here is a very simple code that shows the issue:
def test_keyword_args():
def fn1(a, b, c):
return a + b
def fn2(a, b, c):
return a + c
def fn3(a, b, c):
return b + c
obj = {
'a': fn1,
'b': fn2,
'c': fn3,
}
for key in obj:
value = obj[key](a=1, b=2, c=3)
if key == 'a':
assert value == 3
if key == 'b':
assert value == 4
if key == 'c':
assert value == 5
How can I always call same function obj[key](a=1,b=2,c=3) passing this keyword arguments, and avoid complains about unused parameters? (c not used in fn1, b not used in fn2, a not used in fn3)
I can imagine suppressing warnings would do the trick, but I do not think it is the appropriate solution
I am using Python 3.7.3
You can define arguments as keyword only by prefixing the argument list with *, you can then avoid the unused parameter warnings by naming a parameter _. Using **_ allows us to ignore any keyword arguments not in our named parameters
def fn1(*, a, b, **_):
return a + b
def fn2(*, a, c, **_):
return a + c
def fn3(*, b, c, **_):
return b + c
You may use kwargs to pass key word arguments to a function. In such case kwargs is the dictionary with named arguments passed to function.
def fn1(a, b, **kwargs):
return a + b
You can pass anything to this function in format fn1(a_value, b_value, any_parameter_1=100, any_parameter_2=100) etc. In function you receive your variables a and b and also kwargs dictionary with following content
{
"any_parameter_1": 100,
"any_parameter_2": 1000,
}
Also you may pass all variables as kwargs
def fn1(**kwargs):
return kwargs["a"] + kwargs["b"]
But you need to assign names to your parameters like this fn1(a=a_value, b=b_value, any_parameter_1=100, any_parameter_2=100) and kwargs dictionary will looks like
{
"a": a_value,
"b": b_value,
"any_parameter_1": 100,
"any_parameter_2": 1000,
}
Following is an example of what I would like to do.
def f(a, b, c):
if a == 'method1':
c = 0
return b + c
In this function, parameter c is unneeded if the condition a='method1' is satisfied.
Still, I can call the function with f(a='method1', b=1, c=2), which will have the same effect as f(a='method1', b=1, c=0).
A solution to this is to set the parameter c default to 0
def f(a, b, c=0):
if a == 'method1':
c = 0
return b + c
Now I can call f(a='method1',b=1), which is exactly what I want. The problem is, I can still change the parameter c in the call f(a='method1',b=1,c=1), which I do not want the user to be able to.
Can I enforce this condition in the function signature, and not in the body (i.e. I would not like to use if in the body). Or if there is another better solution, please tell.
Something like
def f(a, b, c = 0 if a == 'method1', else c is required):
return b + c
Thanks in advance.
a, b and c are all assigned dynamically at runtime. There is no way you can make up for this in the signature. It needs to be detected at runtime and you might as well do that in an if as anywhere else. You can specialize at the function name level, though, and python will take care of detecting the number of parameters.
def f(b,c):
return b + c
def f_method1(b):
return f(b, 0)
def f_method2(half_a_c):
return f(0, half_a_c*2)
Hmm... this almost seems like something that you should be able to do with functools.singledispatch and typing.Literal, but I can't quite get them to work together at least in python 3.7 (with Literal coming from the typing_extensions module). I think that in general singledispatch is probably the only tool that will really get what you've asked for as the different registered functions can have entirely different signatures. However, to do this our methods need to be different classes.
from functools import singledispatch
class Method1():
pass
class OtherMethods():
pass
#singledispatch
def f(method, b, c):
return b + c
#f.register(Method1)
def _(method, b):
return b
f(Method1(), 12) # returns 12
f(Method1(), 12, 7) # raises an error
f(OtherMethods(), 12) # raises an error
f(OtherMethods(), 12, 7) # returns 19
Now this is not exactly what you asked for but the arguments are enforced in the signature.
If someone who knows more about the implementation of singledispatch and Literal comes by maybe they can explain the interaction between the two.
One easier thing to do would be to simply define c to default to some invalid value.
def f(a, b, c=None):
if a == 'method1':
c = 0
return b + c
This solves the problem that if the user forgets to set c for a method besides method1 they'll receive a (possibly cryptic) error message. However it doesn't fix the fact that if they set c when using method1 that value will be silently ignored and possibly cause confusion.
Say I have some arguments passed to a function, I use those arguments to do some calculations, and then pass the results to another function, where they are further used. How would I go about passing the results back to the first function and skipping to a point such that data is not sent back to the second function to avoid getting stuck in a loop.
The two functions are in two different python scripts.
The way I'm currently doing it is by adding any new arguments supposed to be coming from the second script as non keyword arguments, and passing all the arguments from the first function to the second even if they are not needed in the second. The second function passes all the arguments back to the first, and an if condition on the non keyword argument to check whether it has its default value is used to determine if the data has been sent back by the second function.
In f1.py:
def calc1(a, b, c, d = []):
a = a+b
c = a*c
import f2
f2.calc2(a, b, c)
If d != []: # This checks whether data has been sent by the second argument, in which case d will not have its default value
print(b, d) # This should print the results from f2, so 'b' should
# retain its value from calc1.
return
In the another script (f2.py)
def calc2(a, b, c):
d = a + c
import f1
f1.calc1(a, b, c, d) # So even though 'b' wasn't used it is there in
# f2 to be sent back to calc1
return
Having two methods recursively call each other is usually a bad idea. It's especially bad between two files. It looks like you want to call calc1(), have it call calc2() internally, and then make a decision about what to do based on the result of calc2().
Is this what you are trying to do?
#### f1.py
import f2
def calc1(a, b, c):
a = a+b
c = a*c
d = f2.calc2(a, b, c)
# This checks whether data has been sent by the second argument,
# in which case d will not have its default value
if d:
# This should print the results from f2, so 'b' should retain
# its value from calc1.
print(b, d)
#### f2.py
def calc2(a, b, c):
return a + c
Let's say I have a method with a few optional parameters.
def foo(a, b=1, c=2, d=3)
How do I go about calling it so that if my variables are None or empty strings the defaults are used?
Conditionals like the following seems like a horrible solution:
if b and not c and d:
foo(myA, b = myB, d = myD)
elif b and not c and not d:
...
In Java I'd jump for a factory, but it seems like that's what defaults are supposed to avoid in this case.
I would change foo so it replaces empty values with default ones.
def foo(a, b=None, c=None, d=None):
if not b: b = 1
if not c: c = 2
if not d: d = 3
Note that this will treat all "false-y" values as defaults, meaning not only None and '' but also 0, False, [], etc. Personally I would tighten the interface up and use None and only None as a default value.
def foo(a, b=None, c=None, d=None):
if b is None: b = 1
if c is None: c = 2
if d is None: d = 3
Though I agree that changing the method is a better idea, here's an alternative that changes the calling part by using a dict of arguments, which is filtered and then unpacked:
d = {'b': myB, 'd': myD}
foo(myA, **{k: d[k] for k in d if d[k]})
Of course if d[k] can be replaced by if d[k] not in {None, ''} for example, which has a slightly different meaning (as pointed out by others).
If you want to catch ONLY None and '':
def foo(a, b, c, d):
blacklist = set([None, ''])
if b in blacklist:
b = 1
if c in blacklist:
c = 2
if d in blacklist:
d = 3
If you want to catch all values v such that bool(v) is False, then:
def foo(a, b, c, d):
b = b or 1
c = c or 2
d = d or 3
Or you could decorate the function with another function that does the assertions for you (which may or may not be overkill, based on your use case)
You could call a function that filters out the variables you don't want passed down
def arg_filter(**kw):
return dict((k,v) for k,v in kw.items() if v not in (None, ''))
foo(**arg_filter(a=1,b=None,c=''))
Not fully tested, but should act as a base:
import inspect
from functools import wraps
def force_default(f):
#wraps(f)
def func(*args, **kwargs):
ca = inspect.getcallargs(f, *args, **kwargs)
da = inspect.getargspec(f)
dv = dict(zip(reversed(da.args), reversed(da.defaults)))
for k, v in ca.iteritems():
if v is None or v == '':
ca[k] = dv[k]
return f(**ca)
return func
#force_default
def foo(a, b=1, c=2, d=3):
print a, b, c, d
foo(6, '', None, 'rabbit!')
# 6 1 2 rabbit!