Re: collecting attributes - what's wrong ? lambda binding ?

Tim Peters (tim@ksr.com)
Wed, 23 Feb 94 16:21:15 EST

> ...
> def collect2( seq, attrib ):
> return map( lambda obj: getattr( obj, attrib ), seq )
>
> - BUT if called from the defined function 'collect2', it gets
> a name error on 'attrib' :
>
> >>> collect2( testSeq, 'me' )
> Traceback (innermost last):
> File "<stdin>", line 1
> File "./collect.py", line 16
> return map( lambda obj: getattr( obj, attrib ), seq )
> File "./collect.py", line 16
> return map( lambda obj: getattr( obj, attrib ), seq )
> NameError: attrib
> ...
> [ Am I wrong ? *SHOULDN'T* this work !_? ]

Question 4.5 of the FAQ touches on this briefly.

The problem is that the visibility rules for lambda functions are the
same as those for nested functions. I.e., within the lambda inside
collect2:

1) The local namespace contains only the argument name ('obj')

2) The global namespace is the module namespace

3) The builtin namespace is the builtin namespace (one rule you can
always count on <grin>), and 'getattr' is found there

'attrib' isn't in any of these (it's in collect2's local namespace),
and those are all the places Python looks -- so the lambda can't find
'attrib'.

Knowing all that suggests three workarounds, alas all ugly as sin:

collect3 puts attrib into the lambda's global namespace, via assignment
to a global (module-level) variable:

def collect3( seq, attrib ):
global gattrib
gattrib = attrib
return map( lambda obj: getattr( obj, gattrib ), seq )

collect4 evaluates attrib within collect4's local namespace, and embeds
the string value into an eval'ed lambda <yeech!>:

def collect4( seq, attrib ):
return map( eval( 'lambda obj: getattr( obj,' + `attrib` + ')' ),
seq )

collect5 is less obvious than those, & manages to avoid globals and
evals, but at the cost of obscurity:

def collect5( seq, attrib ):
return map( lambda obj, attrib: getattr( obj, attrib ),
seq,
[attrib] * len(seq) )

BTW, the reason you didn't have any trouble when typing things in from
the command line is that all your variable names were in the global
namespace then, and lambda expressions have no trouble finding those.

Like you, I've found that map and filter very often can't be used cleanly
inside a function, because they can't see the function's locals. And I
don't see a clean way to get around that given the visibility rules.
Perhaps the local namespace for lambda "should be" inherited from
lambda's caller, fiddled to include the argument bindings. At least that
would push the ugliness into the implementation <wink>.

deeper-than-it-looks!-ly y'rs - tim

Tim Peters tim@ksr.com
not speaking for Kendall Square Research Corp