Difference call function with asterisk parameter and without
Let args = [1,2,3]
:
func(*args) == func(1,2,3)
- variables are unpacked out of list (or any other sequence type) as parameters
func(args) == func([1,2,3])
- the list is passed
Let kwargs = dict(a=1,b=2,c=3)
:
func(kwargs) == func({'a':1, 'b':2, 'c':3})
- the dict is passed
func(*kwargs) == func(('a','b','c'))
- tuple of the dict's keys (in random order)
func(**kwargs) == func(a=1,b=2,c=3)
- (key, value) are unpacked out of the dict (or any other mapping type) as named parameters
Asterisk in function call
*
is the "splat" operator: It takes an iterable like a list as input, and expands it into actual positional arguments in the function call.
So if uniqueCrossTabs
were [[1, 2], [3, 4]]
, then itertools.chain(*uniqueCrossTabs)
is the same as saying itertools.chain([1, 2], [3, 4])
This is obviously different from passing in just uniqueCrossTabs
. In your case, you have a list of lists that you wish to flatten; what itertools.chain()
does is return an iterator over the concatenation of all the positional arguments you pass to it, where each positional argument is iterable in its own right.
In other words, you want to pass each list in uniqueCrossTabs
as an argument to chain()
, which will chain them together, but you don't have the lists in separate variables, so you use the *
operator to expand the list of lists into several list arguments.
chain.from_iterable()
is better-suited for this operation, as it assumes a single iterable of iterables to begin with. Your code then becomes simply:
uniqueCrossTabs = list(itertools.chain.from_iterable(uniqueCrossTabs))
Bare asterisk in function arguments?
Bare *
is used to force the caller to use named arguments - so you cannot define a function with *
as an argument when you have no following keyword arguments.
See this answer or Python 3 documentation for more details.
Python are multiple *(star/asterisk) allowed inside a function call
You have flake8
installed for Python 2 only, so it is analyzing your code according to Python 2's rules. See http://flake8.pycqa.org/en/latest/user/invocation.html for using flake8
to use different versions of Python you may have installed.
However, you can write your code in such a way that it will run identically under both Python 2 and Python 3.
from itertools import chain
result = [fn(*chain(args, dargs),
**dict(chain(kwargs.items(), dkwargs.items())))
for dargs in dynamic_args
for dkwargs in dynamic_kwargs]
What does ** (double star/asterisk) and * (star/asterisk) do for parameters?
The *args
and **kwargs
is a common idiom to allow arbitrary number of arguments to functions as described in the section more on defining functions in the Python documentation.
The *args
will give you all function parameters as a tuple:
def foo(*args):
for a in args:
print(a)
foo(1)
# 1
foo(1,2,3)
# 1
# 2
# 3
The **kwargs
will give you all
keyword arguments except for those corresponding to a formal parameter as a dictionary.
def bar(**kwargs):
for a in kwargs:
print(a, kwargs[a])
bar(name='one', age=27)
# name one
# age 27
Both idioms can be mixed with normal arguments to allow a set of fixed and some variable arguments:
def foo(kind, *args, **kwargs):
pass
It is also possible to use this the other way around:
def foo(a, b, c):
print(a, b, c)
obj = {'b':10, 'c':'lee'}
foo(100,**obj)
# 100 10 lee
Another usage of the *l
idiom is to unpack argument lists when calling a function.
def foo(bar, lee):
print(bar, lee)
l = [1,2]
foo(*l)
# 1 2
In Python 3 it is possible to use *l
on the left side of an assignment (Extended Iterable Unpacking), though it gives a list instead of a tuple in this context:
first, *rest = [1,2,3,4]
first, *l, last = [1,2,3,4]
Also Python 3 adds new semantic (refer PEP 3102):
def func(arg1, arg2, arg3, *, kwarg1, kwarg2):
pass
For example the following works in python 3 but not python 2:
>>> x = [1, 2]
>>> [*x]
[1, 2]
>>> [*x, 3, 4]
[1, 2, 3, 4]
>>> x = {1:1, 2:2}
>>> x
{1: 1, 2: 2}
>>> {**x, 3:3, 4:4}
{1: 1, 2: 2, 3: 3, 4: 4}
Such function accepts only 3 positional arguments, and everything after *
can only be passed as keyword arguments.
Note:
- A Python
dict
, semantically used for keyword argument passing, are arbitrarily ordered. However, in Python 3.6, keyword arguments are guaranteed to remember insertion order. - "The order of elements in
**kwargs
now corresponds to the order in which keyword arguments were passed to the function." - What’s New In Python 3.6 - In fact, all dicts in CPython 3.6 will remember insertion order as an implementation detail, this becomes standard in Python 3.7.
Putting asterisk before function call in python
Consider this completely contrived example.
def list_to_tuple(l):
return l[0], l[1]
def output(x, y):
print(x, y)
l = [1, 2]
output(*list_to_tuple(l))
Here, it looks like the *
is "before the function call", but really it is before the value returned by the function call. In this case, a tuple. So the asterisk, unpacks that tuple's values into the parameters for output
.
What does the star and doublestar operator mean in a function call?
The single star *
unpacks the sequence/collection into positional arguments, so you can do this:
def sum(a, b):
return a + b
values = (1, 2)
s = sum(*values)
This will unpack the tuple so that it actually executes as:
s = sum(1, 2)
The double star **
does the same, only using a dictionary and thus named arguments:
values = { 'a': 1, 'b': 2 }
s = sum(**values)
You can also combine:
def sum(a, b, c, d):
return a + b + c + d
values1 = (1, 2)
values2 = { 'c': 10, 'd': 15 }
s = sum(*values1, **values2)
will execute as:
s = sum(1, 2, c=10, d=15)
Also see section 4.7.4 - Unpacking Argument Lists of the Python documentation.
Additionally you can define functions to take *x
and **y
arguments, this allows a function to accept any number of positional and/or named arguments that aren't specifically named in the declaration.
Example:
def sum(*values):
s = 0
for v in values:
s = s + v
return s
s = sum(1, 2, 3, 4, 5)
or with **
:
def get_a(**values):
return values['a']
s = get_a(a=1, b=2) # returns 1
this can allow you to specify a large number of optional parameters without having to declare them.
And again, you can combine:
def sum(*values, **options):
s = 0
for i in values:
s = s + i
if "neg" in options:
if options["neg"]:
s = -s
return s
s = sum(1, 2, 3, 4, 5) # returns 15
s = sum(1, 2, 3, 4, 5, neg=True) # returns -15
s = sum(1, 2, 3, 4, 5, neg=False) # returns 15
Python - detect asterisk (*) during function call
That's a strange quirk of unpacking I hadn't noticed before.
Now that I dug into the details of CPython to see what's up:
- A
foo(*bar)
call gets turned into anCALL_FUNCTION_EX
opcode. - These are handled here in ceval.c, where
Py_SETREF(callargs, PySequence_Tuple(callargs));
is done to get a tuple of call arguments. PySequence_Tuple
inabstract.c
does some checks and callsPyObject_GetIter(v);
to get an iterator of the object to use to create a tuple.PyObject_GetIter
does a check forif (PySequence_Check(o)) return PySeqIter_New(o);
.PySequence_Check
looks at whetherPy_TYPE(s)->tp_as_sequence && Py_TYPE(s)->tp_as_sequence->sq_item != NULL
...- and from typeobject.c we can see that
sq_item
corresponds to__getitem__
. PySeqIter_New()
returns a sequence iterator, which is an iterator that apparently calls__getitem__
untilStopIteration
gets raised.
As an aside,
thats by the way also strange, that python doesnt protect you with recursion max-depth
it's because there's no recursion here. The new MyClass
es you instantiate are dutifully packed onto the (future) args array of the some_function
call, not used for anything.
Either way, you could circumvent this by declaring __iter__
on the class. It seems unpacking will prefer attempting to iterate over the object.
class MyClass:
def __init__(self,key=None):
self.registered_key = key
def __getitem__(self,key):
# ...
def __iter__(self):
return iter([0])
What do * (single star) and / (slash) do as independent parameters?
There is a new function parameter syntax /
to indicate that some function parameters must be specified positionally and cannot be used as keyword arguments.[This is new in Python 3.8
]
Documentation specifies some of the use cases/benefits of positional-only parameters.
It allows pure Python functions to fully emulate behaviors of
existing C coded functions. For example, the built-inpow()
function does not accept keyword arguments:def pow(x, y, z=None, /):
"Emulate the built in pow() function"
r = x ** y
return r if z is None else r%zAnother use case is to preclude keyword arguments when the parameter
name is not helpful. For example, the builtinlen()
function has
the signaturelen(obj, /)
. This precludes awkward calls such as:len(obj='hello') # The "obj" keyword argument impairs readability
A further benefit of marking a parameter as positional-only is that
it allows the parameter name to be changed in the future without
risk of breaking client code. For example, in the statistics module,
the parameter name dist may be changed in the future. This was made
possible with the following function specification:def quantiles(dist, /, *, n=4, method='exclusive')
...
Where as *
is used to force the caller to use named arguments. This is one of the use case of named arguments.
So, given the method,
def func(self, param1, param2, /, param3, *, param4, param5):
print(param1, param2, param3, param4, param5)
It must called with
obj.func(10, 20, 30, param4=50, param5=60)
or
obj.func(10, 20, param3=30, param4=50, param5=60)
ie,
param1
,param2
must be specified positionally.param3
can be called either with positional or keyword.param4
andparam5
must be called with keyword argument.
DEMO:
>>> class MyClass(object):
... def func(self, param1, param2, /, param3, *, param4, param5):
... return param1, param2, param3, param4, param5
...
>>> obj = MyClass()
>>>
>>> assert obj.func(10, 20, 30, param4=40, param5=50), obj.func(
... 10, 20, param3=30, param4=40, param5=50
... )
Related Topics
Unicodedecodeerror: 'Utf8' Codec Can't Decode Byte 0Xa5 in Position 0: Invalid Start Byte
Is There a List of Pytz Timezones
Changing User Agent on Urllib2.Urlopen
How to Do a Recursive Sub-Folder Search and Return Files in a List
Comparing Two Numpy Arrays for Equality, Element-Wise
How to Extract the N-Th Elements from a List of Tuples
How to Avoid Circular Imports in Python
Python List Multiplication: [[...]]*3 Makes 3 Lists Which Mirror Each Other When Modified
Checking Whether a String Starts with Xxxx
How to Generate Keyboard Events
Importerror: No Module Named Requests
Nested Arguments Not Compiling
Python: Pandas Merge Multiple Dataframes