Closures in a class scope

2019-04-30 01:47发布

问题:

From my understanding, function and class scopes behave pretty much the same:

>>> def x():
...     a = 123
...     print (locals())
... 
>>> x()
{'a': 123}


>>> class x():
...     a = 123
...     print (locals())
... 
{'a': 123, '__module__': '__main__'}

When, however, I define a closure, behaviors are different. A function simply returns the local binding, as expected:

>>> def x():
...     a = 123
...     t = lambda: a
...     return t
... 
>>> x()()
123

whereas in a class the binding appears to be lost:

>>> class x():
...     a = 123
...     t = lambda self: a
... 
>>> x().t()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in <lambda>
NameError: global name 'a' is not defined

Can anyone explain the discrepancy?

回答1:

The class scope is a temporary scope, it only exists while executing the body of the class definition. The resulting dict is used to create the class namespace, the __dict__ of the class.

As far as functions defined in a class are concerned, the next scope 'up' is the scope of the class definition itself.

The following works fine:

>>> def foo():
...     spam = 'eggs'
...     class Bar(object):
...         def baz(self): return spam
...     return Bar()
... 
>>> foo().baz()
'eggs'

This is documented in pep 227:

Names in class scope are not accessible. Names are resolved in the innermost enclosing function scope. If a class definition occurs in a chain of nested scopes, the resolution process skips class definitions.

and in the class compound statement documentation:

The class’s suite is then executed in a new execution frame (see section Naming and binding), using a newly created local namespace and the original global namespace. (Usually, the suite contains only function definitions.) When the class’s suite finishes execution, its execution frame is discarded but its local namespace is saved. [4] A class object is then created using the inheritance list for the base classes and the saved local namespace for the attribute dictionary.

Emphasis mine; the execution frame is the temporary scope.