list of lambda

J

jena

hello,
when i create list of lambdas:
l=[lambda:x.upper() for x in ['a','b','c']]
then l[0]() returns 'C', i think, it should be 'A'

my workaround is to define helper class with __call__ method:
class X:
def __init__(self,s): self.s=s
def __call__(self): return self.s.upper()
l=[X(x) for x in ['a','b','c']]

now it is correct, l[0]()=='A'

it is OK or it is bug?
can i do it correctly simplier than with helper X class?

thanks
Honza Prochazka
 
L

Leif K-Brooks

jena said:
hello,
when i create list of lambdas:
l=[lambda:x.upper() for x in ['a','b','c']]
then l[0]() returns 'C', i think, it should be 'A'

Fredrik Lundh provided the general solution, but in this specific case,
the simplest solution is:

l = [x.upper for x in ['a', 'b', 'c']]
 
P

Paul Rubin

jena said:
l=[lambda:x.upper() for x in ['a','b','c']]
then l[0]() returns 'C', i think, it should be 'A'

Yeah, this is Python late binding, a standard thing to get confused
over. You want:

l = [lambda x=x: x.upper() for x in ['a', 'b', 'c']]
 
B

bonono

Leif said:
jena said:
hello,
when i create list of lambdas:
l=[lambda:x.upper() for x in ['a','b','c']]
then l[0]() returns 'C', i think, it should be 'A'

Fredrik Lundh provided the general solution, but in this specific case,
the simplest solution is:

l = [x.upper for x in ['a', 'b', 'c']]

how about :

l = ['A','B','C']
 
S

Steven D'Aprano

hello,
when i create list of lambdas:
l=[lambda:x.upper() for x in ['a','b','c']]
then l[0]() returns 'C', i think, it should be 'A'

What is wrong with just doing this?

L = [x.upper() for x in ['a', 'b', 'c']]



py> L = [lambda: x.upper() for x in ['a', 'b', 'c']]
py> L
[QUOTE= said:
L[0]() 'C'
L[1]() 'C'
L[2]()
[/QUOTE]
'C'

What you have discovered is a bug in your code, caused by some accidental
behaviour of Python which will be removed in a new version soon:

py> [x.upper() for x in "abc"]
['A', 'B', 'C']
py> x
'c'

You can see that the temporary variable x used by the list comprehension
is exposed. It shouldn't be, and soon won't be -- it will be an error to
refer to the list comp variable outside the list comp.

Now watch this:

py> x = "it is was a mistake to expose list comprehension variables"
py> L[0]()
'IT IS WAS A MISTAKE TO EXPOSE LIST COMPREHENSION VARIABLES'
py> L[1]()
'IT IS WAS A MISTAKE TO EXPOSE LIST COMPREHENSION VARIABLES'

Do you see what is going on now?


Assuming you actually do need a list of *functions*, rather than just
the results of those functions, this would be the way to do it:

lambda x: x.upper()

is an anonymous function which takes input x and returns x converted to
upper case.

lambda x: x.upper # note the brackets are gone

is an anonymous function which takes input x and returns a function
(technically, a method) which will return x converted to upper case when
called.

So the list comprehension you want is:


# note all the brackets
py> L = [(lambda x: x.upper)(x) for x in ['a', 'b', 'c']]
py> L
[<built-in method upper of str object at 0xf706a040>, <built-in method upper of str object at 0xf706a0e0>, <built-in method upper of str object at 0xf706ca00>]
py> L[0](); L[1](); L[2]()
'A'
'B'
'C'

But now that gives us a clue that using lambda just adds too much
complication! What we want is the string methods, and we don't need lambda
to get them. So we can make it much simpler:

py> L = [x.upper for x in ['a', 'b', 'c']]
py> L
[<built-in method upper of str object at 0xf706a040>, <built-in method upper of str object at 0xf706a0e0>, <built-in method upper of str object at 0xf706ca00>]
py> L[0](); L[1](); L[2]()
'A'
'B'
'C'

Hope this helps.
 
B

Bengt Richter

jena said:
l=[lambda:x.upper() for x in ['a','b','c']]
then l[0]() returns 'C', i think, it should be 'A'

Yeah, this is Python late binding, a standard thing to get confused
over. You want:

l = [lambda x=x: x.upper() for x in ['a', 'b', 'c']]

or if you want the upper() eagerly (and do it only once each,
in case of multiple lambda calls)

l = [lambda x=x.upper():x for x in ['a', 'b', 'c']]
>>> l = [lambda x=x.upper():x for x in ['a', 'b', 'c']]
>>> for lamb in l: print lamb.func_defaults[0],'=?=',lamb()
...
A =?= A
B =?= B
C =?= C

Regards,
Bengt Richter
 
B

Bengt Richter

hello,
when i create list of lambdas:
l=[lambda:x.upper() for x in ['a','b','c']]
then l[0]() returns 'C', i think, it should be 'A'

What is wrong with just doing this?

L = [x.upper() for x in ['a', 'b', 'c']]



py> L = [lambda: x.upper() for x in ['a', 'b', 'c']]
py> L
[QUOTE= said:
L[0]() 'C'
L[1]() 'C'
L[2]()
'C'

What you have discovered is a bug in your code, caused by some accidental
behaviour of Python which will be removed in a new version soon:

py> [x.upper() for x in "abc"]
['A', 'B', 'C']
py> x
'c'

You can see that the temporary variable x used by the list comprehension
is exposed. It shouldn't be, and soon won't be -- it will be an error to
refer to the list comp variable outside the list comp.

Now watch this:

py> x = "it is was a mistake to expose list comprehension variables"
py> L[0]()
'IT IS WAS A MISTAKE TO EXPOSE LIST COMPREHENSION VARIABLES'
py> L[1]()
'IT IS WAS A MISTAKE TO EXPOSE LIST COMPREHENSION VARIABLES'

Do you see what is going on now?


Assuming you actually do need a list of *functions*, rather than just
the results of those functions, this would be the way to do it:

lambda x: x.upper()

is an anonymous function which takes input x and returns x converted to
upper case.

lambda x: x.upper # note the brackets are gone

is an anonymous function which takes input x and returns a function
(technically, a method) which will return x converted to upper case when
called.

So the list comprehension you want is:


# note all the brackets
py> L = [(lambda x: x.upper)(x) for x in ['a', 'b', 'c']]
py> L
[<built-in method upper of str object at 0xf706a040>, <built-in method upper of str object at 0xf706a0e0>, <built-in method upper of str object at 0xf706ca00>]
py> L[0](); L[1](); L[2]()
'A'
'B'
'C'

But now that gives us a clue that using lambda just adds too much
complication! What we want is the string methods, and we don't need lambda
to get them. So we can make it much simpler:

py> L = [x.upper for x in ['a', 'b', 'c']]
py> L
[<built-in method upper of str object at 0xf706a040>, <built-in method upper of str object at 0xf706a0e0>, <built-in method upper of str object at 0xf706ca00>]
py> L[0](); L[1](); L[2]()
'A'
'B'
'C'

Hope this helps.
[/QUOTE]
Yes, but it exposes bad (improvable;-) repr text IMO:
It's not just a method, it's a _bound_ method, but the repr text
doesn't say so (unless you read clues between the lines)
'a'
<method 'upper' of 'str' objects>

That's the unbound method. If we bind it to 'a' in the usual way
behind inst.method,
>>> type('a')
>>> type('a').__dict__
>>> type('a').__dict__['upper']
>>> type('a').__dict__['upper'].__get__('a', type('a'))
<built-in method upper of str object at 0x02EB03A0>

Or <built-in method upper of str object at 0x02EB03A0>

we get the bound method. So the clue is "... of str objects" vs ".. of str object at ..."
Maybe nicer would be
<bound built-in method upper of str object at 0x02EB03A0>

Same if it's inherited: 'A'

But if we override, we get 'bound method ...'
... def upper(self): return 'S.upper => %r' % str.upper(self)
... "S.upper => 'A'"

A nit. I thought it clever to replace the lambda with the the bound method,
but while supplying a callable, it still postpones the upper execution, and
will repeat it for each call, whereas lambda x=x.upper():x does the work once
up front (in general not always possible, of course).

Regards,
Bengt Richter
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top