Path: csiph.com!usenet.pasdenom.info!weretis.net!feeder4.news.weretis.net!ecngs!feeder2.ecngs.de!newsfeed.freenet.ag!news2.euro.net!newsgate.cistron.nl!newsgate.news.xs4all.nl!post.news.xs4all.nl!not-for-mail Return-Path: X-Original-To: python-list@python.org Delivered-To: python-list@mail.python.org X-Spam-Status: OK 0.000 X-Spam-Evidence: '*H*': 1.00; '*S*': 0.00; 'reject': 0.05; 'test,': 0.05; 'definitions': 0.07; 'exec': 0.07; 'executable': 0.07; 'executed': 0.07; 'expressions': 0.07; 'parameter': 0.07; 'problem:': 0.07; 'repeated': 0.07; 'tests,': 0.07; 'unittest': 0.07; 'python': 0.09; '__future__': 0.09; "ain't": 0.09; 'arg': 0.09; 'behave': 0.09; 'called.': 0.09; 'failure.': 0.09; 'methods,': 0.09; 'objects.': 0.09; 'received:80.91': 0.09; 'received:80.91.229': 0.09; 'received:gmane.org': 0.09; 'received:list': 0.09; 'solution,': 0.09; 'stable.': 0.09; 'substitution': 0.09; 'terry': 0.09; 'textwrap': 0.09; 'def': 0.10; '3):': 0.16; 'binding.': 0.16; 'eckhardt': 0.16; 'executed.': 0.16; 'expect,': 0.16; 'gonna': 0.16; 'inconvenient': 0.16; 'matters,': 0.16; 'names*': 0.16; 'parameter,': 0.16; 'received:80.91.229.3': 0.16; 'received:plane.gmane.org': 0.16; 'reedy': 0.16; 'substituted': 0.16; 'surprising': 0.16; 'test()': 0.16; '{0}': 0.16; 'later': 0.16; 'wrote:': 0.17; 'fix': 0.17; 'resolved': 0.17; 'test.': 0.17; 'tests.': 0.17; 'creates': 0.18; 'examples': 0.18; 'jan': 0.18; 'tests': 0.18; '>>>': 0.18; 'code,': 0.18; 'parameters': 0.20; 'putting': 0.20; 'import': 0.21; 'explicit': 0.22; 'names.': 0.22; 'produces': 0.22; 'defined': 0.22; "i'd": 0.22; 'example': 0.23; 'statement': 0.23; 'this:': 0.23; 'testing': 0.24; 'header:In-Reply-To:1': 0.25; 'header:User-Agent:1': 0.26; 'values': 0.26; 'am,': 0.27; 'easiest': 0.27; 'executing': 0.27; 'execution': 0.27; 'functions.': 0.27; 'correct': 0.28; 'header:X-Complaints-To:1': 0.28; 'run': 0.28; 'exclude': 0.29; 'methods.': 0.29; 'parameters.': 0.29; 'statements': 0.29; 'usable': 0.29; 'definition': 0.29; 'class': 0.29; 'this.': 0.29; "i'm": 0.29; "skip:' 10": 0.30; 'ends': 0.30; 'framework': 0.30; 'keyword': 0.30; 'function': 0.30; 'error': 0.30; 'expect': 0.31; 'code': 0.31; '(and': 0.32; 'you?': 0.32; "skip:' 20": 0.32; 'running': 0.32; 'could': 0.32; 'raising': 0.33; 'problem': 0.33; 'to:addr :python-list': 0.33; 'another': 0.33; 'changed': 0.34; 'text': 0.34; 'list': 0.35; 'sometimes': 0.35; 'something': 0.35; 'there': 0.35; 'received:org': 0.36; 'really': 0.36; 'but': 0.36; 'skip:{ 10': 0.36; 'test': 0.36; 'should': 0.36; 'skip:p 20': 0.36; 'enough': 0.36; 'possible': 0.37; 'itself': 0.37; 'does': 0.37; 'uses': 0.37; 'why': 0.37; 'late': 0.37; 'quite': 0.37; 'subject:: ': 0.38; 'unit': 0.38; 'solve': 0.62; 'different': 0.63; 'times': 0.63; 'skip:n 10': 0.63; 'more': 0.63; 'within': 0.64; 'behavior': 0.64; 'surprise': 0.65; 'fact,': 0.69; 'bodies': 0.71; 'further,': 0.71; 'lol.': 0.71; 'obvious': 0.71; 'failures.': 0.84; 'obvious.': 0.84; 'received:fios.verizon.net': 0.84; 'spell': 0.91; 'many,': 0.93 X-Injected-Via-Gmane: http://gmane.org/ To: python-list@python.org From: Terry Reedy Subject: Re: puzzled by name binding in local function Date: Tue, 05 Feb 2013 14:27:12 -0500 References: Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit X-Gmane-NNTP-Posting-Host: pool-173-75-251-66.phlapa.fios.verizon.net User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:17.0) Gecko/17.0 Thunderbird/17.0 In-Reply-To: X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: General discussion list for the Python programming language List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Newsgroups: comp.lang.python Message-ID: Lines: 162 NNTP-Posting-Host: 2001:888:2000:d::a6 X-Trace: 1360092463 news.xs4all.nl 6847 [2001:888:2000:d::a6]:34751 X-Complaints-To: abuse@xs4all.nl Xref: csiph.com comp.lang.python:38219 Code examples are Python 3 On 2/5/2013 10:18 AM, Ulrich Eckhardt wrote: > 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 Names* in Python code are resolved when the code is executed. Function bodies are executed when the function is called. Ergo, names in function bodies are resolved when the function is called. This is sometimes called late binding. * This may exclude keyword names. Late binding of global names within functions is why the following can work instead of raising NameError. >>> def f(): print(x) >>> x = 3 >>> f() 3 Only the most recent binding of x, at the time of the call matters, as long as there is one. Does the following really surprise you? >>> x = 0 >>> def f(): print(x) >>> x = 3 >>> f() 3 What do you expect this to print? >>> x = 1 >>> def f1(): print(x) >>> x = 2 >>> def f2(): print(x) >>> x = 3 >>> f1(), f2() Rolling the repeated code into a loop does not magically change the behavior of def statements. for i in range(1, 3): exec('''\ x = {0} def f{0}(): print(x)'''.format(i)) x = 3 print((f1(), f2())) This gives *exactly* the same output. So does this: from textwrap import dedent for i in range(1, 3): exec(dedent(''' x = {0} def f{0}(): print(x) '''.format(i))) x = 3 print((f1(), f2())) Python does not do text substitution unless you explicit ask it too, as I did above. Late binding is also why functions (and methods, such as .__init__) can call functions (methods) whose definitions follow later in the code, so don't change that this change ;-). > and not substituted inside the for-loop, > 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 A good test framework should give specifics as to the failure. The unittest assertxxx methods do this. In fact, emitting specific messages is one reason there are so many methods. The real 'problem' with multiple tests within a test function is that the first failure ends that group of tests. But this is only a problem during development when there *are* failures. And it is possible to write a test function to run all tests and collect multiple error messages before 'failing' the test. > which others don't. Further, I want to be able to run just that one > because the tests take time. Whether multiple tests are buried within one function or many, running just one of them will require some editing. > * 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. LOL. You know the easiest and correct solution, but reject it because it is not 'obvious' - though it was obvious enough for you to see it. If one understands that function definition are executable statements and that their execution is not magically changed by putting them inside loops, the problem with your code should be obvious. It creates 5 *identical* functions objects. So it should not be surprising that they behave identically. > I'd prefer something more stable. The fact that default arg expressions are evaluated when the function is defined is quite stable. Ain't gonna change. > Any other suggestions? Revise your obvious meter ;-). > # 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) Executing this n times produces n identical functions. The easy fix is def test(self, j = i): self.util(param = j) > setattr(Foo, 'test_{}'.format(i), test) Another fix that should work: adapt my code above and use exec within a loop within the class statement itself (and delete setattr). for i in range(5): exec(dedent(''' def test_{0}(self): self.util(param={0}) '''.format(i))) > unittest.main() -- Terry Jan Reedy