Force function caller decide which default value is used - python

Lets say I have the following function:
def myFun(a=None, b=None):
print a, b
Now I call the function:
myFun("hi") # Case 1
>>>hi None
myFun(b="hi") # Case 2
>>>None hi
myFun(a="hi") # Case 3
>>>hi None
Is there a way to throw an exception if the function caller did not decide which variable the value "hi" is assigned to? That means I would like to have an exception in the 1st case but not in the 2nd and 3rd case. I use python 2.7.

In Python3 you can specify keyword-only arguments:
def myFun(*, a=None, b=None):
print(a, b)
myFun('hi')
raises
TypeError: myFun() takes 0 positional arguments but 1 was given
In Python2, as Ricardo Silveira points out you could use **kwargs to force all arguments to be keyword arguments.
def myFun(**kwargs):
a, b = kwargs.get('a'), kwargs.get('b')
print(a, b)
myFun('hi')
# TypeError: myFun() takes exactly 0 arguments (1 given)

def my_fun(**kwargs):
a = kwargs.get("a", None)
b = kwargs.get("b", None)
# have your code here...
Then: my_fun("hi") shouldn't work...

You can actually allow the user to provide any arguments they'd like by using **kwargs.
def myFun(**kwargs):
print kwargs[a]
print kwargs[b]
This will cause an error if a or b aren't defined, but they're not very helpful.
You can make your own errors by checking if the value exists. For example:
def myFun(**kwargs):
if not kwargs.get(a):
raise Exception('a is not here!')
if not kwargs.get(b):
raise Exception ('b is not here!')
print kwargs[a], kwargs[b]

Related

Count positional arguments in function signature

I have the following function signature:
# my signature
def myfunction(x, s, c=None, d=None):
# some irrelevant function body
I need the number of positional arguments. How can I return the number (2) of positional arguments (x & s). The number of keyword arguments is not relevant.
You can get the number of all arguments (using f.__code__.co_argcount), the number of keyword arguments (using f.__defaults__), and subtract the latter from the first:
def myfunction(x, s, c=None, d=None):
pass
all_args = myfunction.__code__.co_argcount
if myfunction.__defaults__ is not None: # in case there are no kwargs
kwargs = len(myfunction.__defaults__)
else:
kwargs = 0
print(all_args - kwargs)
Output:
2
From the Docs:
__defaults__: A tuple containing default argument values for those arguments that have defaults, or None if no arguments have a default value.
and:
co_argcount is the number of positional arguments (including arguments with default values);
You could do that by using the inspect module like,
>>> import inspect
>>> def foo(a, b, *, c=None):
... print(a, b, c)
...
>>> sig = inspect.signature(foo)
>>> sum(1 for param in sig.parameters.values() if param.kind == param.POSITIONAL_OR_KEYWORD)
2
The caveat is,
Python has no explicit syntax for defining positional-only parameters,
but many built-in and extension module functions (especially those
that accept only one or two parameters) accept them.

Python argument matching for f(a, *b)

def f(a,*b):
print(a,b)
for the function f defined as above, if I call f(1, *(2,3)) it prints 1, (2,3) as expected.
However calling f(a=1, *(2,3)) causes an error:
TypeError: f() got multiple values for argument 'a'
Any positional argument can also be supplied as an explicit keyword argument.
There should be only one interpretation for f(a=1, *(2,3)) without ambiguity.
def f(a,*b):
print(a,b)
f(1,*(2,3))
f(1,2,3)
consider the example above both will call the same function in the same way
now if you specify a =1
f(a=1,2,3)
#or in other syntax
f(2,3,a=1)
then it has an ambiguity to whether to consider a=1 or a=2 since 2 is the first positional argument and a=1 is an explicit keyword argument .

Pass arguments only when needed

As an example I have a function f and a variable a=1.
Sometimes f needs a and I want to call f(a=a) and other times f has no arguments. How can I deal with that?
When I try to call f(a=a) (expecting that it will silently ignore the case where a is not used in f), I get the following TypeError:
f got an unexpected keyword argument 'a'
Edit
I want to implement a function g such that, given:
def f1(a):
return a
def f2():
return 1
we have:
g(f1, a) == a
g(f2, a) == 1
It seems you want to inspect a function's arguments.
For that, use the inspect module:
import inspect
def g(somefunc, *params):
num_args = len(inspect.getargspec(somefunc).args)
return somefunc(*params[:num_args])
Testing, with your functions:
def f1(a):
return a
def f2():
return 1
>>> print(g(f1, 3))
3
>>> print(g(f2, 3))
1
use keyword dictionary in f:
def f(**kwargs):
if 'a' in kwargs:
print("a passed with value {}".format(kwargs['a']))
else:
print('a not passed')
f(a=12)
f()
prints:
a passed with value 12
a not passed
it makes argument testing/retrieving completely manual. You can do anything you want, and it can be generalized to several arguments.
This also forbids to pass arguments as positional like f(12) which is probably a good thing in your case.
>>> f(12)
Traceback (most recent call last):
TypeError: f() takes 0 positional arguments but 1 was given
The drawback is that the caller cannot rely on parameter names to know what to pass. Either create a docstring, or people will have to read the code/guess...
Now to apply that to your edit, which kind of changes the problem I must say. So f2 cannot be changed, in that cas the work must be done in the wrapper function:
def f1(a):
return a
def f2():
return 1
def g(f,arg):
try:
return f(a=arg)
except TypeError:
return f()
a=12
print(g(f1, a))
print(g(f2, a))
prints:
12
1
so "better ask forgiveness than permission": if we get a TypeError (raised when the parameter isn't known by the function), we just call the function without the parameter. The drawback if that if the first function returns a TypeError, we cannot know if it was the parameters or inside the function.
A workaround would be to inspect the error message and only call the function without parameters if the error message is about parameters:
def g(f,arg):
try:
return f(a=arg)
except TypeError as e:
if "unexpected keyword" in str(e):
return f()
else:
raise e # another TypeError, let it pass
Simply specify the default argument in the function def line...
def f(a=1):
return(a)
print(f()) #returns 1
print(f(a = 2)) #returns 2
For your next part (as you seemed to have switched your question with edits...
def g(f, a):
return(f(a))

TypeError: fun() takes at least 2 arguments (3 given)

This is my code:
def fun(x, y, b=None, c=None):
print(x,' ',y,' ',b,' ',c)
I am calling it as fun(1, b=2, c=4) and getting error TypeError: fun() takes at least 2 arguments (3 given).
I know this error is because of incorrect number of positional and keyword arguments.
Instead of this, I want whenever I call my function with incorrect no. of arguments, it should tell me which argument is provided.
For example: for above case it should say something like "argument y is nor provided".
Can I write a decorator for this purpose?
fun as defined need to get between 2 and 4 argument, as it has two mandatory arguments and two optional arguments. You did not provide one of the two mandatory ones:
fun(1, b=2, c=4) # What about the argument y?
You need to call it using one of the next forms:
fun(1, 2)
fun(1, 2, b=3)
fun(1, 2, c=4)
fun(1, 2, b=3, c=4)
If you want notification about insufficient arguments, you can use args and kwargs:
def fun(*args, **kwargs):
if len(args) < 2:
print("Need at least two arguments!"); return
if len(args) > 2 or len(kwargs) > 2:
print("Too much arguments supplied!"); return
x, y = args
a, b = kwargs.get('a', None), kwargs.get('b', None)
print(x, y, a, b)
I want to handle this error and prompt error like it requires these (names) positional arguments. Is it possible to write a decorator for this?
I did a bit of research and came across the inspect module. Perhaps something along these lines will suffice? Right now I'm catching TypeError and printing a message, but you may prefer throwing a new TypeError that contains the message instead.
import inspect
from functools import wraps
def inspect_signature(f):
signature = inspect.signature(f)
#wraps(f)
def decorator(*args, **kwargs):
try:
f(*args, **kwargs)
except TypeError:
print('Failed to call "{}" with signature {}. Provided args={} and kwargs={}.'.format(
f.__name__, signature, args, kwargs))
return decorator
#inspect_signature
def func(foo, bar):
print('Called successfully with foo={}, bar={}'.format(foo, bar))
pass
if __name__ == '__main__':
func(foo='a', bar='b')
func(foo='a')
func('a', 'b', 'c')
Output
Called successfully with foo=a, bar=b
Failed to call "func" with signature (foo, bar). Provided args=() and kwargs={'foo': 'a'}.
Failed to call "func" with signature (foo, bar). Provided args=('a', 'b', 'c') and kwargs={}.

Python optional function argument to default to another argument's value

I want to define a function with some optional arguments, let's say A (mandatory) and B (optional). When B is not given, I want it to take the same value as A. How could I do that?
I have tried this, but it doesn't work (name 'B' is not defined):
def foo(A, B=A):
do_something()
I understand that the values of the arguments are not assigned before the body of the function.
You shall do this inside of your function.
Taking your original function:
def foo(A, B=A):
do_something()
try something like:
def foo(A, B=None):
if B is None:
B = A
do_something()
Important thing is, that function default values for function arguments are given at the time, the function is defined.
When you call the function with some value for A, it is too late as the B default value was already assigned and lives in function definition.
You could do it like this. If B has a value of None then assign it from A
def foo(A, B=None):
if B is None:
B = A
print 'A = %r' % A
print 'B = %r' % B

Categories

Resources