*args treated as single argument - python

I'm trying to make a function designed to call another function multiple times:
def iterator(iterations, function, *args):
#called as:
iterator(5, my_function, arg1, arg2, arg3)
Note that the number of arguments here is variable: could 1, could be 2, could be 10.
fill them in based on the function that is being called.
def iterator(iterations, function, *args):
for i in range(iteration):
temp = function(args)
return temp
The problem here is:
TypeError: my_function() takes exactly 4 arguments (1 given)
And this is because (arg1, arg2, arg3, arg4) are being treated as a single argument.
How do I get around this?

By using the same syntax when applying the args sequence:
temp = function(*args)
The *args syntax here is closely related to the *args function parameter syntax; instead of capturing an arbitrary number of arguments, using *args in a call expands the sequence to separate arguments.
You may be interested to know that there is a **kwargs syntax too, to capture and apply keyword arguments:
def iterator(iterations, function, *args, **kwargs):
for i in range(iteration):
temp = function(*args, **kwargs)
return temp

Try this, unpacking the argument list (a.k.a. splatting it):
function(*args)
From the example in the documentation, you'll see that this is what you need:
range(3, 6) # ok
range([3, 6]) # won't work
range(*[3, 6]) # it works!

Related

passing **kwargs to a de-referencing function

Hope the title is conveying the correct information.
My problem is that I don't understand why call kwarg_function(some_func, a=1, b=2, c=3) fails. I would have thought that as 'c' isn't referenced with some_func() it would simply be ignored. Can anyone explain why 'c' isn't simply ignored.
def kwarg_function(function, **kwargs):
print(kwargs)
function(**kwargs)
def some_func(a, b):
print(f"type: {type(a)} values: {a}")
print(f"type: {type(b)} values: {b}")
kwarg_function(some_func, a=1, b=2) # called successfully
kwarg_function(some_func, a=1, b=2, c=3) # fails with unexpected keyword arg 'c'
Think of the ** as "unpack what's on my right side as keyword arguments" in this case.
def foo(a,b,**kwargs):
# This will take any number of arguments provided that starting from the 3rd one they are keyword args
# Those are equivalent (and working) calls
foo(1,2, x = 7)
foo(1,2, **{"x":7})
# Those will fail
foo(1,2,7)
foo(1,2, {"x":7})
The function you declared expects 2 arguments
def some_func(a, b):
And you are calling it with three under the hood, because this:
kwarg_function(some_func, a=1, b=2, c=3) # fails with unexpected keyword arg 'c'
Does this (inside kwarg_function body):
funtion(a=1,b=2,c=3)
In python, * and ** are for unpacking iterables. They don't consider what's are in them, and just unpack whatever you pass in them.
You can find more info about it in this link.
So, when you pass a=1, b=2, c=3, ... as kwargs to your kwargs_function, you will get them as kwargs param, regardless of what you have passed.
And then, when you pass **kwargs to another function, all of your data would be passed to your another function, regardless of what's in that.
If you want your some_func be more flexible with your data and accept whatever you pass to it, you can add **kwargs param to it too:
def some_func(a, b, **kwargs):
print(f"type: {type(a)} values: {a}")
print(f"type: {type(b)} values: {b}")

Why Does Python Allow *args After Keyword Arguments?

Example:
def foo(a, b=2, *args, **kwargs): pass
Why does this not result in a SyntaxError? *args will not catch additional non-keyword arguments because it is illegal to pass them after keyword arguments.
For python3.x the correct use of *args, **kwargs in this case looks like:
def foo(a, *args, b=2, **kwargs): pass
Thanks for any insights into this curious behavior.
Edit:
Thanks to Jab for pointing me to PEP 3102, which explains this behavior concisely. Check it out!
And also thanks to jsbueno for the additional excellent explanation, which I am updating as the best answer due to its thoroughness.
Given:
def foo(a, b=2, *args, **kwargs): pass
b is not a keyword-only parameter - it is just a parameter for which arguments can be positional or named, but have a default value. It is not possible to pass any value into args and omit passing b or passing b out of order in the signature you suggest.
This signature makes sense and is quite unambiguous - you can pass from 0 to n positional arguments, but if you pass 2 or more, the second argument is assigned to "b", and end of story.
If you pass 0 positional arguments, you can still assign values to "a" or "b" as named arguments, but trying anything like: foo(0, 1, 2, a=3, b=4) will fail as more than one value is attempted to be passed to both parameters.
Where as in:
def foo(a, *args, b=2, **kwargs): pass
it is also an unambiguous situation: the first positional argument goes to "a", the others go to "args", and you can only pass a value to "b" as a named argument.
The new / syntax in signature definition coming with Python 3.8 gives more flexibility to this, allowing one to require that "a" and "b" are passed as positional-only arguments. Again, there is no ambiguity:
def foo(a, b=2, /, *args, **kwargs): pass
A curious thing on this new syntax: one is allowed to pass named arguments to "a" and "b", but the named arguments will come up as key/value pairs inside "kwargs" - while the local variables "a" and "b" will be assigned the positional only arguments:
def foo(a, b=2, /, *args, **kwargs):
print(a, b, args, kwargs)
...
In [9]: foo(1, 2, a=3, b=4)
1 2 () {'a': 3, 'b': 4}
Whereas with the traditional syntax you ask about - def foo(a, b=2, *args, **kwargs): - one gets a TypeError if that is tried:
In [11]: foo(1,2, a=3, b=4)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-11-d002c7717dba> in <module>
----> 1 foo(1,2, a=3, b=4)
TypeError: foo() got multiple values for argument 'a'
This was implemented into 3.X for multiple reasons. Best way I can answer this is refer to
PEP 3102
Also take a look at the New Syntax section in the Python 3.0.1 docs.
TLDR:
Named parameters occurring after
*args in the parameter list must be specified using keyword syntax in the call. You can also use a bare * in the parameter list to indicate
that you don’t accept a variable-length argument list, but you do have
keyword-only arguments.

How to use *args and self in Python constructor

I need a Python method to have access to self for instance variables and also be able to take any number of arguments. I basically want a method foo that can be called via
foo(a, b, c)
or
foo()
In the class, I think the constructor would be
def foo(self, *args):
Is this correct? Also, fyi, I am new to Python (if you can't tell).
You just have to add it after the self parameter:
class YourClass:
def foo(self, *args):
print(args)
def bar(self, *args, **kwargs):
print(args)
print(kwargs)
def baz(self, **kwargs):
print(kwargs)
I have also added a method in which you also add **kwargs, and the case in which you add both *args and **kwargs.
Examples
>>> o = YourClass()
>>> o.foo()
()
>>> o.foo(1)
(1,)
>>> o.foo(1, 2)
(1, 2)
def foo(self, *args):
Yes, that is correct.
You declared the method correctly. You can also use double asterisks to accept keyword arguments.
Reference: Expressions
A double asterisk ** denotes dictionary unpacking. Its operand must be a mapping. Each mapping item is added to the new dictionary. Later values replace values already set by earlier key/datum pairs and earlier dictionary unpackings.
....
An asterisk * denotes iterable unpacking. Its operand must be an iterable. The iterable is expanded into a sequence of items, which are included in the new tuple, list, or set, at the site of the unpacking.
Args will be a tuple. To access the values you will have to iterate or use positional arguments, ie: args[0]

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={}.

Wrapping a functions taking a variable number of arguments using a decorator

I want to create a decorator which can wrap around functions with different numbers of variables. For example, below I have two functions abc and abcd, taking 2 and 3 arguments respectively. I want the wrapper to take a variable number of arguments in order to deal with this. However, when I run the code, I get TypeError: _wrapper() takes exactly 0 arguments (2 given), as though the **kwargs was ignored.
Why is this, and how do I fix this?
def dec(function):
def _wrapper(**kwargs):
print len(kwargs)
function(**kwargs)
return _wrapper
#dec
def abc(a, b):
return a*b
#dec
def abcd(a, b, c):
return a*b*c
Python has two variable argument variables: *args and **kwargs. args is a list of arguments specified without names (e.g. func(1,2,3)), while kwargs is a dictionary of arguments with names (e.g. func(x=1, y=2, z=3)).
Your code is only accepting the kwargs, so you also need to add the unnamed args:
def dec(function):
def _wrapper(*args, **kwargs):
print len(args) + len(kwargs)
return function(*args, **kwargs)
return _wrapper

Categories

Resources