How to get the decorator handle arbitrary arguments - python

I am trying to understand decorators. I want to define a decorator that can handle any arbitrary argument. I am trying the following:
def a_decorator_passing_arbitrary_arguments(function_to_decorate):
def a_wrapper_accepting_arbitrary_arguments(*args,**kwargs):
print('The positional arguments are', args)
print('The keyword arguments are', kwargs)
function_to_decorate(*args)
return a_wrapper_accepting_arbitrary_arguments
This is based on this tutorial and it supposedly handles any type of argument. However when I pass only keyword arguments I get the following output with function f(a,b,c):
#a_decorator_passing_arbitrary_arguments
def f(a,b,c):
print("The arguments here are the following: {0}, {1}, {2}.".format(a,b,c))
f(a='ali', b='emma', c='t')
The output:
The positional arguments are ()
The keyword arguments are {'a': 'ali', 'b': 'emma', 'c': 't'}
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-3-cc5ee8d7120a> in <module>
----> 1 f(a='ali', b='emma', c='t')
<ipython-input-1-af03800e2abd> in a_wrapper_accepting_arbitrary_arguments(*args, **kwargs)
3 print('The positional arguments are', args)
4 print('The keyword arguments are', kwargs)
----> 5 function_to_decorate(*args)
6 return a_wrapper_accepting_arbitrary_arguments
TypeError: f() missing 3 required positional arguments: 'a', 'b', and 'c'
How can I avoid getting this error in the case when all the variables are passed as keyword arguments?

You are currently only passing on the positional arguments, not the keyword arguments as well, to the wrapped function.
def a_decorator_passing_arbitrary_arguments(function_to_decorate):
def a_wrapper_accepting_arbitrary_arguments(*args,**kwargs):
print('The positional arguments are', args)
print('The keyword arguments are', kwargs)
# function_to_decorate(*args) # Wrong
return function_to_decorate(*args, **kwargs) # Right
return a_wrapper_accepting_arbitrary_arguments
(You should also return whatever function_to_decorate returns from the wrapper.)

Related

why did this error occur when argument is under the scope of the function f()?

def f(a, *arguments):
for arg in arguments:
print(arg)
return arg
f(5)
Error:
Traceback (most recent call last):
File "test.py", line 6, in <module>
f(5)
File "test.py", line 5, in f
return arg
UnboundLocalError: local variable 'arg' referenced before assignment
if you change the function f() like this
def f(a, *arguments):
for arg in range(1,4):
print(arg)
return arg
f(5)
the output is this:
1
2
3
now arg is global in the function. Why did this happen?
def f(a, *arguments):
for arg in arguments:
print(arg)
return arg
f(5)
Since *arguments is empty, you never enter the loop. Thus, when you reach return(arg) the variable is still undefined. Try calling it with f(5, [1, 2, 3]) and you'll see something more like your expectations.
#Prune's answer is apt for your question. You may still want to return something even if arguments is empty. You can do so by adding a check for arguments length.
def f(a, *arguments):
if len(arguments):
for arg in arguments:
print(arg)
return arg # will return last element in arguments always
return -1 # will return default value here
f(5)

"TypeError: foo() takes exactly 1 argument" when executing Pool.map() with kwargs

I'm creating a functools.partial for a function with **kwargs.
from functools import partial
def foo(required, **kwargs):
return required + str(kwargs)
_foo = partial(foo, "hello", bar='baz')
Both foo("hello", bar='baz') and _foo() print the expected output:
In [4]: foo("hello", bar="baz")
Out[4]: "hello{'bar': 'baz'}"
In [5]: _foo()
Out[5]: "hello{'bar': 'baz'}"
I attempt to run this partial as part of a multiprocessing.Pool:
import multiprocessing as mp
pool = mp.Pool()
results = pool.map(_foo, range(2)) # Run the _foo partial twice
But I get:
TypeError: foo() takes exactly 1 argument (3 given)
How can I execute foo in a pool and provide the desired keyword arguments?
You first bind the positional argument required, and then in the map call, another positional argument is passed implicitly (0 in first call, 1 in second call).
Those are, of course, invalid calls, as is easy to demonstrate regardless of the pool:
_foo = partial(foo, "hello", bar='baz') # required is bound to "hello" here
_foo(0) # no more position arguments are allowed...
=> TypeError: foo() takes exactly 1 argument (3 given)

Can you have keyword arguments without supplying a default value?

I am used to having function/method definitions like so in Python:
def my_function(arg1=None , arg2='default'):
... do stuff here
If I don't supply arg1 (or arg2), then the default value of None (or 'default') is assigned.
Can I specify keyword arguments like this, but without a default value? I would expect it to raise an error if the argument was not supplied.
You can in modern Python (3, that is):
>>> def func(*, name1, name2):
... print(name1, name2)
...
>>> func()
Traceback (most recent call last):
File "<ipython-input-5-08a2da4138f6>", line 1, in <module>
func()
TypeError: func() missing 2 required keyword-only arguments: 'name1' and 'name2'
>>> func("Fred", "Bob")
Traceback (most recent call last):
File "<ipython-input-7-14386ea74437>", line 1, in <module>
func("Fred", "Bob")
TypeError: func() takes 0 positional arguments but 2 were given
>>> func(name1="Fred", name2="Bob")
Fred Bob
Any argument can be given as with a keyword expression, whether or not it has a default:
def foo(a, b):
return a - b
foo(2, 1) # Returns 1
foo(a=2, b=1) # Returns 1
foo(b=2, a=1) # Returns -1
foo() # Raises an error
If you want to force the arguments to be keyword-only, then see DSM's answer, but that didn't seem like what you were really asking.

Python - Noob Question - What does it mean "Giving one of the optional arguments"?

What does this do?
giving one of the optional arguments:
ask_ok('OK to overwrite the file?', 2)
def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
while True:
ok = input(prompt)
if ok in ('y', 'ye', 'yes'):
return True
if ok in ('n', 'no', 'nop', 'nope'):
return False
retries = retries - 1
if retries < 0:
raise IOError('refusenik user')
print(complaint)
See tutorial:http://docs.python.org/py3k/tutorial/controlflow.html#default-argument-values
In Python there are several types of arguments:
positional and keyword
named and arbitrary
The function argument in Python is an attribution operation, that is, arguments are assigned to variables in the function local namespace.
If you have a declaration like this:
def some_func(pos_arg1, pos_arg2, kw_arg1=1, kw_arg2='test'):
print "postional arg 1 =", pos_arg1
print "postional arg 2 =", pos_arg2
print "keyword arg 1 =", kw_arg1
print "keyword arg 2 =", kw_arg2
Positional arguments are mandatory and will be assigned in the given order, but keyword arguments are optional and can be called in any order - when omitted, named keyword arguments assume the declared default values (1 and 'test' in the example). So far:
>>> some_func(1) # positional arguments are mandatory
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
TypeError: some_func() takes at least 2 arguments (1 given)
>>> some_func(1, 2) # this is ok
postional arg 1 = 1
postional arg 2 = 2
keyword arg 1 = 1
keyword arg 2 = test
>>> some_func(1, 2, 3) # this is also ok, keyword args may work like positional
postional arg 1 = 1
postional arg 2 = 2
keyword arg 1 = 3
keyword arg 2 = test
>>> some_func(1, 2, 3, 4) # this is also ok, keyword args may work like positional
postional arg 1 = 1
postional arg 2 = 2
keyword arg 1 = 3
keyword arg 2 = 4
>>> some_func(1, 2, kw_arg2=3) # kyword arguments may be given in any order
postional arg 1 = 1
postional arg 2 = 2
keyword arg 1 = 1
keyword arg 2 = 3
There is a problem with undeclared arguments:
>>> some_func(1, 2, 3, 4, 5)
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
TypeError: some_func() takes at most 4 arguments (5 given)
But you can have an arbitrary number of arguments in using the special form * and **:
>>> def some_func(pos_arg1, pos_arg2, *args, **kw_args):
... print "postional arg 1 =", pos_arg1
... print "postional arg 2 =", pos_arg2
... print "other positional orgs =", args
... print "other keyword args =", kw_args
...
>>> some_func(1, 2, 3, 4, 5) # any number of arguments
postional arg 1 = 1
postional arg 2 = 2
other positional orgs = (3, 4, 5)
other keyword args = {}
>>> some_func(1, 2, a=3, x=4, y=5) # * and ** are optional
postional arg 1 = 1
postional arg 2 = 2
other positional orgs = ()
other keyword args = {'a': 3, 'x': 4, 'y': 5}
>>> some_func(1, 2, 'banana', 'orange', 'apple', a=3, x=4, y=5)
postional arg 1 = 1
postional arg 2 = 2
other positional orgs = ('banana', 'orange', 'apple')
other keyword args = {'a': 3, 'x': 4, 'y': 5}
>>>
The * argument will be available as a tuple of positional arguments, and ** will be a dict of keyword arguments.
You can mix everything together but there is a rule: all keyword arguments have to be declared after positional arguments, and all the arbitrary must be after named ones.
Sets retries to 2 and leaves complaint on the default value 'Yes or no, please!'. The order of the optional arguments in the first line of the function definition is important.
This is a function.
If you are learning python I recommend you first read a good book like "Learning python". Start with simple tutorial and read a lot. After read a lot read more and more. Python is a beatifull language to start programming. Sometimes, like me, you will thing in some others languages but stay with python and you can have good results.

Python pack arguments?

is to possible to "pack" arguments in python? I have the following functions in the library, that I can't change (simplified):
def g(a,b=2):
print a,b
def f(arg):
g(arg)
I can do
o={'a':10,'b':20}
g(**o)
10 20
but can I/how do I pass this through f?
That's what I don't want:
f(**o)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() got an unexpected keyword argument 'a'
f(o)
{'a': 10, 'b': 20} 2
f has to accept arbitrary (positional and) keyword arguments:
def f(*args, **kwargs):
g(*args, **kwargs)
If you don't want f to accept positional arguments, leave out the *args part.

Categories