What Is the Good Python3 Equivalent for Auto Tuple Unpacking in Lambda

What is the good python3 equivalent for auto tuple unpacking in lambda?

No, there is no other way. You covered it all. The way to go would be to raise this issue on the Python ideas mailing list, but be prepared to argue a lot over there to gain some traction.

Actually, just not to say "there is no way out", a third way could be to implement one more level of lambda calling just to unfold the parameters - but that would be at once more inefficient and harder to read than your two suggestions:

min(points, key=lambda p: (lambda x,y: (x*x + y*y))(*p))

Python 3.8 update

Since the release of Python 3.8, PEP 572 — assignment expressions — have been available as a tool.

So, if one uses a trick to execute multiple expressions inside a lambda - I usually do that by creating a tuple and just returning the last component of it, it is possible to do the following:

>>> a = lambda p:(x:=p[0], y:=p[1], x ** 2 + y ** 2)[-1]
>>> a((3,4))
25

One should keep in mind that this kind of code will seldom be more readable or practical than having a full function. Still, there are possible uses - if there are various one-liners that would operate on this point, it could be worth to have a namedtuple, and use the assignment expression to effectively "cast" the incoming sequence to the namedtuple:

>>> from collections import namedtuple
>>> point = namedtuple("point", "x y")
>>> b = lambda s: (p:=point(*s), p.x ** 2 + p.y ** 2)[-1]

processing a tuple in lambda expression in list comprehensions

The syntax you are attempting works in Python 2.x. It won't work in Python 3.

In Python 3, you can use positional indexing:

from operator import itemgetter

records = [('bob', 35, 'manager'), ('sue', 40, 'developer')]

ages1 = list(map(lambda x: x[1], records)) # [35, 40]
ages2 = list(map(itemgetter(1), records)) # [35, 40]

Better (more efficient, more readable) is to abandon lambda altogether. You can use a list comprehension with sequence unpacking:

ages3 = [age for _, age, _ in records]       # [35, 40]

You can even use zip if you don't mind a tuple:

_, ages4, _ = zip(*records)                  # (35, 40)

For a cleaner version of the last solution, you can use itertools.islice:

from itertools import islice

ages5 = next(islice(zip(*records), 1, 2)) # (35, 40)

Python: Is there syntax-level support for unpacking, from tuples, the arguments to an *anonymous* function?

starmap! It's in the itertools package.

From my examples:

list(map(lambda t: foo(*t), listoftuples))

becomes

list(starmap(foo, listoftuples))

See why it's called starmap? And with anonymous functions:

def unpackInto(func): return lambda t: func(*t)
list(map(unpackInto(lambda a, b, c: a*b%c), listoftuples))

becomes

list(starmap(lambda a, b, c: a*b%c, listoftuples))

A simple four-letter prefix, star, is the "asterisk" I was looking for.

So, yes, there is standard library support for unpacking parameters from tuples into anonymous functions but only for map, via starmap.

No, there is no syntax-level support, but there was syntax level support in python 2. See Bakuriu's answer. The last would become:

list(map(lambda (a, b, c): a*b%c, listoftuples))

Which is even more concise. It's almost a shame it had to be taken away.

Then again, lambda is rarely as concise or readable as a list comprehension:

[a*b%c for a, b, c in listoftuples]

How to migrate lambda from python2 to python3 and handle removed tuple parameter unpacking in python3

The direct approach is to index into the tuple:

lambda x_y_z: (x_y_z[0][0], x_y_z[1], x_y_z[2])

You can mechanically apply this approach in any case that unpacks an indexable sequence argument, like a tuple or list. I think this one is kind of ugly, but it's fine as a first step to get your tests passing on Python 3 before a more thorough refactor.

If you're upgrading by hand anyway, you could instead nest lambdas to emulate parameter unpacking:

lambda x_y_z: (lambda x, y, z: x[0], y, z)(*x_y_z)

It's debatable whether this is more or less readable than the direct approach. I find the names easier to read than the numbers, but it's at the cost of a more complex expression. You probably shouldn't always upgrade lambdas this way because nested parameter tuples would take too many lambdas to unpack. But this approach would also be able to unpack iterators, which can't be directly indexed.

Note that you can still unpack as before by using a def in advance and an assignment statement, which may be the most readable:

def spam(x_y_z):
x, y, z = x_y_z
return x[0], y, z

This approach will also work on iterator arguments and for nested tuples. But you will have to come up with an appropriate function name each time.


can you explain me the meaning of *x_y_z ?

In that context (inside a call), the * passes the contents of an iterable object as positional arguments, instead of the iterable itself. Maybe an example would help:

>>> def show_args(*args):
... print(args)
...
>>> show_args([1,2,3], 'abc')
([1, 2, 3], 'abc')
>>> show_args(*[1,2,3], *'abc')
(1, 2, 3, 'a', 'b', 'c')

lambda with reduce error in Python3

tuple pattern matching was removed from python 3, so try:

reduce(lambda z, x: (z+1) if x[0] == x[1] else z, [(3, 4), (5, 2), (4, 4), (5, 5)], 0)

As @cᴏʟᴅsᴘᴇᴇᴅ commented, check PEP3113

This works perfectly in python2.x but not on python3.x

I'm surprised it supposedly works in 2.x, but apparently explicit tuples were ok in function arguments. But this was never great style. Function arguments should just be separated by commas. This works in both:

def add_pair(pair1, pair2): 
a, b = pair1
c, d = pair2
return a+c, b+d

print(add_pair((10, 20), (30, 40)))

Using a tuple as a parameter of lambda throws an error about a missing required positional argument

My answer is a little late, but I hope that maybe it will be helpful for the future.
Yes, you are right. The problem with the translation from Python 2 --> Python 3 is the fact that lambdas in Python 3 do not accept tuples as arguments.
However, there is a solution and this is done by having the expected sequence argument bound to a single parameter and then indexing on that parameter :

lambda (i,x):i-x

will become :

lambda x: x[1]-x[0]

Therefore, the code which works in Python 3 will be :

from operator import itemgetter
import itertools
# Find runs of consecutive numbers using groupby. The key to the solution
# is differencing with a range so that consecutive numbers all appear in
# same group.
L = [ 1, 4,5,6, 10, 15,16,17,18, 22, 25,26,27,28]
for k, g in itertools.groupby( enumerate(L), lambda x: x[1]-x[0] ) :
print (list(map(itemgetter(1), g)))

which will output :

[1]
[4, 5, 6]
[10]
[15, 16, 17, 18]
[22]
[25, 26, 27, 28]

Please see more about lambda with tuples here :
python3-equivalent-for-auto-tuple-unpacking-in-lambda

Tuple Unpacking with List Comprehension fails but works with for-loop

@PaulPanzer That appears to work. I will have to verify that everything lines up correctly. But why do I need that?

Say q is an iterable for which (?) your comprehension produces a list with 26 tuples, and each tuple has 4 items.

z = [(a,b,c,d) for i, (a,b,c,d,*e) in enumerate(q)]

In [6]: len(z)
Out[6]: 26

In [7]: len(z[0])
Out[7]: 4

In [17]: z[:3]
Out[17]: [('a', 'a', 'a', 'a'), ('b', 'b', 'b', 'b'), ('c', 'c', 'c', 'c')]

When you try to unpack you are trying to stuff 26 items into four names/variables

In [8]: a,b,c,d = z
Traceback (most recent call last):

File "<ipython-input-8-64277b78f273>", line 1, in <module>
a,b,c,d = z

ValueError: too many values to unpack (expected 4)

zip(*list_of_4_item_tuples) will transpose the list_of_4_item_tuples to 4 tuples with 26 items each

In [9]: 

In [9]: a,b,c,d = zip(*z) # z is the result of the list comprehension shown above

In [11]: len(a),len(b),len(c),len(d)
Out[11]: (26, 26, 26, 26)

Test stuff

import string
a = string.ascii_lowercase
b = string.ascii_lowercase
c = string.ascii_lowercase
d = string.ascii_lowercase
e = string.ascii_lowercase
f = string.ascii_lowercase
q = zip (a,b,c,d,e,f)


Related Topics



Leave a reply



Submit