Asterisk in Function Call

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:

  1. A foo(*bar) call gets turned into an CALL_FUNCTION_EX opcode.
  2. These are handled here in ceval.c, where Py_SETREF(callargs, PySequence_Tuple(callargs)); is done to get a tuple of call arguments.
  3. PySequence_Tuple in abstract.c does some checks and calls PyObject_GetIter(v); to get an iterator of the object to use to create a tuple.
  4. PyObject_GetIter does a check for if (PySequence_Check(o)) return PySeqIter_New(o);.
  5. PySequence_Check looks at whether Py_TYPE(s)->tp_as_sequence && Py_TYPE(s)->tp_as_sequence->sq_item != NULL ...
  6. and from typeobject.c we can see that sq_item corresponds to __getitem__.
  7. PySeqIter_New() returns a sequence iterator, which is an iterator that apparently calls __getitem__ until StopIteration 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 MyClasses 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.

  1. It allows pure Python functions to fully emulate behaviors of
    existing C coded functions. For example, the built-in pow()
    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%z
  2. Another use case is to preclude keyword arguments when the parameter
    name is not helpful. For example, the builtin len() function has
    the signature len(obj, /). This precludes awkward calls such as:

    len(obj='hello')  # The "obj" keyword argument impairs readability
  3. 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,

  1. param1, param2 must be specified positionally.
  2. param3 can be called either with positional or keyword.
  3. param4 and param5 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



Leave a reply



Submit