puzzled by name binding in local function

Dave Angel davea at davea.name
Tue Feb 5 19:07:23 CET 2013


On 02/05/2013 10:18 AM, Ulrich Eckhardt wrote:
> Hello Pythonistas!
>
> Below you will find example code distilled from a set of unit tests,
> usable with Python 2 or 3. I'm using a loop over a list of parameters to
> generate tests with different permutations of parameters. Instead of
> calling util() with values 0-4 as I would expect, each call uses the
> same parameter 4. What I found out is that the name 'i' is resolved when
> Foo.test_1 is called and not substituted inside the for-loop, which
> finds the global 'i' left over from the loop. A simple "del i" after the
> loop proved this and gave me an according error.
>
> Now, I'm still not sure how to best solve this problem:
>   * Spell out all permutations is a no-go.
>   * Testing the different iterations inside a single test, is
> inconvenient because I want to know which permutation exactly fails and
> which others don't. Further, I want to be able to run just that one
> because the tests take time.
>   * Further, I could generate local test() functions using the current
> value of 'i' as default for a parameter, which is then used in the call
> to self.util(), but that code is just as non-obviously-to-me correct as
> the current code is non-obviously-to-me wrong. I'd prefer something more
> stable.
>
>
> Any other suggestions?
>
> Thank you!
>
> Uli
>
>
> # example code
> from __future__ import print_function
> import unittest
>
> class Foo(unittest.TestCase):
>      def util(self, param):
>          print('util({}, {})'.format(self, param))
>
> for i in range(5):
>      def test(self):
>          self.util(param=i)
>      setattr(Foo, 'test_{}'.format(i), test)
>
> unittest.main()

There is only one instance of i, so it's not clear what you expect. 
Since it's not an argument to test(), it has to be found in the closure 
to the function.  In this case, that's the global namespace.  So each 
time the function is called, it fetches that global.

To put it another way, you're storing the same function object 5 times. 
  If you need to have separate function objects that already know a 
value for i, you need to somehow bind the value into the function object.

One way to do it, as you say, is with default parameters.  A function's 
default parameters are each stored in the object, because they're 
defined to be evaluated only once.  That's sometimes considered a flaw, 
such as when they're volatile, and subsequent calls to the function use 
the same value.  But in your case, it's a feature, as it provides a 
standard place to store values as known at function definition time.

The other way to do it is with functions.partial().  I can't readily 
write you sample code, as I haven't messed with it in the case of class 
methods, but partial is generally a way to bind one or more values into 
the actual object.  I also think it's clearer than the default parameter 
approach.


Notice that globals may be defined after a function that references 
them, which is a way of cross-checking the logic you already discovered. 
  The names are only looked up when the function is actually called.

This same logic applies to nested functions;  the class definition is an 
unnecessary complication;  of course I understand it's needed for unittest.

The main place where I see this type of problem is in a gui, where 
you're defining a callback to be used by a series of widgets, but you 
have a value that IS different for each item in the series.  You write a 
loop much like you did, and discover that the last loop value is the 
only one used.  The two cures above work, and you can also use lambda 
creatively.

-- 
DaveA



More information about the Python-list mailing list