new draft

This commit is contained in:
Jeremy Hylton 2000-12-14 04:50:32 +00:00
parent 1fa85eead2
commit d1917acf5a
1 changed files with 293 additions and 85 deletions

View File

@ -23,103 +23,311 @@ Abstract
statement uses default arguments to explicitly creating bindings
in the lambda's namespace.
Specification
Notes
Python is a statically scoped language with block structure, in
the traditional of Algol. A code block or region, such as a
module, class defintion, or function body, is the basic unit of a
program.
This section describes several issues that will be fleshed out and
addressed in the final draft of the PEP. Until that draft is
ready, please direct comments to the author.
Names refer to objects. Names are introduced by name binding
operations. Each occurrence of a name in the program text refers
to the binding of that name established in the innermost function
block containing the use.
This change has been proposed many times in the past. It has
always been stymied by the possibility of creating cycles that
could not be collected by Python's reference counting garbage
collector. The additional of the cycle collector in Python 2.0
eliminates this concern.
The name binding operations are assignment, class and function
definition, and import statements. Each assignment or import
statement occurs within a block defined by a class or function
definition or at the module level (the top-level code block).
Guido once explained that his original reservation about nested
scopes was a reaction to their overuse in Pascal. In large Pascal
programs he was familiar with, block structure was overused as an
organizing principle for the program, leading to hard-to-read
code.
If a name binding operation occurs anywhere within a code block,
all uses of the name within the block are treated as references to
the current block. (Note: This can lead to errors when a name is
used within a block before it is bound.)
Greg Ewing developed a proposal "Python Nested Lexical Scoping
Enhancement" in Aug. 1999[1]
If the global statement occurs within a block, all uses of the
name specified in the statement refer to the binding of that name
in the top-level namespace. Names are resolved in the top-level
namespace by searching the global namespace, the namespace of the
module containing the code block, and the builtin namespace, the
namespace of the module __builtin__. The global namespace is
searched first. If the name is not found there, the builtin
namespace is searched.
Michael Hudson's bytecodehacks projects[2] provides facilities to
support nested scopes using the closure module.
If a name is used within a code block, but it is not bound there
and is not declared global, the use is treated as a reference to
the nearest enclosing function region. A region is visible from a
block is all enclosing blocks are introduced by function
defintions. (Note: If a region is contained within a class
definition, the name bindings that occur in the class block are
not visible to enclosed functions.)
Examples:
A class definition is an executable statement that may uses and
definitions of names. These references follow the normal rules
for name resolution. The namespace of the class definition
becomes the attribute dictionary of the class.
def make_adder(n):
def adder(x):
return x + n
return adder
add2 = make_adder(2)
add2(5) == 7
Discussion
This proposal changes the rules for resolving free variables in
Python functions. The Python 2.0 definition specifies exactly
three namespaces to check for each name -- the local namespace,
the global namespace, and the builtin namespace. According to
this defintion, if a function A is defined within a function B,
the names bound in B are not visible in A. The proposal changes
the rules so that names bound in B are visible in A (unless A
contains a name binding that hides the binding in B).
from Tkinter import *
root = Tk()
Button(root, text="Click here",
command = lambda : root.test.configure(text="..."))
The specification introduces rules for lexical scoping that are
common in Algol-like languages. The combination of lexical
scoping and existing support for first-class functions is
reminiscent of Scheme.
The changed scoping rules address two problems -- the limited
utility of lambda statements and the frequent confusion of new
users familiar with other languages that support lexical scoping,
e.g. the inability to define recursive functions except at the
module level.
One controversial issue is whether it should be possible to modify
the value of variables defined in an enclosing scope.
The lambda statement introduces an unnamed function that contains
a single statement. It is often used for callback functions. In
the example below (written using the Python 2.0 rules), any name
used in the body of the lambda must be explicitly passed as a
default argument to the lambda.
One part of the issue is how to specify that an assignment in the
local scope should reference to the definition of the variable in
an enclosing scope. Assignment to a variable in the current scope
creates a local variable in the scope. If the assignment is
supposed to refer to a global variable, the global statement must
be used to prevent a local name from being created. Presumably,
another keyword would be required to specify "nearest enclosing
scope."
from Tkinter import *
root = Tk()
Button(root, text="Click here",
command=lambda root=root: root.test.configure(text="..."))
Guido is opposed to allowing modifications (need to clarify
exactly why). If you are modifying variables bound in enclosing
scopes, you should be using a class, he says.
This approach is cumbersome, particularly when there are several
names used in the body of the lambda. The long list of default
arguments obscure the purpose of the code. The proposed solution,
in crude terms, implements the default argument approach
automatically. The "root=root" argument can be omitted.
The problem occurs only when a program attempts to rebind the name
in the enclosing scope. A mutable object, e.g. a list or
dictionary, can be modified by a reference in a nested scope; this
is an obvious consequence of Python's reference semantics. The
ability to change mutable objects leads to an inelegant
workaround: If a program needs to rebind an immutable object,
e.g. a number or tuple, store the object in a list and have all
references to the object use this list:
The specified rules allow names defined in a function to be
referenced in any nested function defined with that function. The
name resolution rules are typical for statically scoped languages,
with three primary exceptions:
def bank_account(initial_balance):
balance = [initial_balance]
def deposit(amount):
balance[0] = balance[0] + amount
def withdraw(amount):
balance[0] = balance[0] - amount
return deposit, withdraw
- Class definitions hide names.
- The global statement short-circuits the normal rules.
- Variables are not declared.
I would prefer for the language to support this style of
programming directly rather than encouraging programs to use this
somewhat obfuscated style. Of course, an instance would probably
be clearer in this case.
Class definitions hide names. Names are resolved in the innermost
enclosing function scope. If a class defintion occurs in a chain
of nested scopes, the resolution process skips class definitions.
This rule prevents odd interactions between class attributes and
local variable access. If a name binding operation occurs in a
class defintion, it creates an attribute on the resulting class
object. To access this variable in a method, or in a function
nested within a method, an attribute reference must be used,
either via self or via the class name.
One implementation issue is how to represent the environment that
stores variables that are referenced by nested scopes. One
possibility is to add a pointer to each frame's statically
enclosing frame and walk the chain of links each time a non-local
variable is accessed. This implementation has some problems,
because access to nonlocal variables is slow and causes garbage to
accumulate unnecessarily. Another possibility is to construct an
environment for each function that provides access to only the
non-local variables. This environment would be explicitly passed
to nested functions.
An alternative would have been to allow name binding in class
scope to behave exactly like name binding in function scope. This
rule would allow class attributes to be referenced either via
attribute reference or simple name. This option was ruled out
because it would have been inconsistent with all other forms of
class and instance attribute access, which always use attribute
references. Code that used simple names would have been obscure.
The global statement short-circuits the normal rules. Under the
proposal, the global statement has exactly the same effect that it
does for Python 2.0. It's behavior is preserved for backwards
compatibility. It is also noteworthy because it allows name
binding operations performed in one block to change bindings in
another block (the module).
References
Variables are not declared. If a name binding operation occurs
anywhere in a function, then that name is treated as local to the
function and all references refer to the local binding. If a
reference occurs before the name is bound, a NameError is raised.
The only kind of declaration is the global statement, which allows
programs to be written using mutable global variables. As a
consequence, it is not possible to rebind a name defined in an
enclosing scope. An assignment operation can only bind a name in
the current scope or in the global scope. The lack of
declarations and the inability to rebind names in enclosing scopes
are unusual for lexically scoped languages; there is typically a
mechanism to create name bindings (e.g. lambda and let in Scheme)
and a mechanism to change the bindings (set! in Scheme).
[1] http://www.cosc.canterbury.ac.nz/~greg/python/lexscope.html
Examples
[2] http://sourceforge.net/projects/bytecodehacks/
A few examples are included to illustrate the way the rules work.
>>> def make_fact():
... def fact(n):
... if n == 1:
... return 1L
... else:
... return n * fact(n - 1)
... return fact
>>> fact = make_fact()
>>> fact(7)
5040L
>>> def make_adder(base):
... def adder(x):
... return base + x
... return adder
>>> add5 = make_adder(5)
>>> add5(6)
11
>>> def make_wrapper(obj):
... class Wrapper:
... def __getattr__(self, attr):
... if attr[0] != '_':
... return getattr(obj, attr)
... else:
... raise AttributeError, attr
... return Wrapper()
>>> class Test:
... public = 2
... _private = 3
>>> w = make_wrapper(Test())
>>> w.public
2
>>> w._private
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: _private
An example from Tim Peters of the potential pitfalls of nested scopes
in the absence of declarations:
i = 6
def f(x):
def g():
print i
# ...
# skip to the next page
# ...
for i in x: # ah, i *is* local to f, so this is what g sees
pass
g()
The call to g() will refer to the variable i bound in f() by the for
loop. If g() is called before the loop is executed, a NameError will
be raised.
Other issues
Backwards compatibility
The proposed changes will break backwards compatibility for some
code. The following example from Skip Montanaro illustrates:
x = 1
def f1():
x = 2
def inner():
print x
inner()
Under the Python 2.0 rules, the print statement inside inner()
refers to the global variable x and will print 1 if f1() is
called. Under the new rules, it refers to the f1()'s namespace,
the nearest enclosing scope with a binding.
The problem occurs only when a global variable and a local
variable share the same name and a nested function uses that name
to refer to the global variable. This is poor programming
practice, because readers will easily confuse the two different
variables.
To address this problem, which is unlikely to occur often, a
static analysis tool that detects affected code will be written.
The detection problem is straightfoward.
locals() / vars()
These functions return a dictionary containing the current scope's
local variables. Modifications to the dictionary do not affect
the values of variables. Under the current rules, the use of
locals() and globals() allows the program to gain access to all
the namespaces in which names are resolved.
An analogous function will not be provided for nested scopes.
Under this proposal, it will not be possible to gain
dictionary-style access to all visible scopes.
Rebinding names in enclosing scopes
There are technical issues that make it difficult to support
rebinding of names in enclosing scopes, but the primary reason
that it is not allowed in the current proposal is that Guido is
opposed to it. It is difficult to support, because it would
require a new mechanism that would allow the programmer to specify
that an assignment in a block is supposed to rebind the name in an
enclosing block; presumably a keyword or special syntax (x := 3)
would make this possible.
The proposed rules allow programmers to achieve the effect of
rebinding, albeit awkwardly. The name that will be effectively
rebound by enclosed functions is bound to a container object. In
place of assignment, the program uses modification of the
container to achieve the desired effect:
def bank_account(initial_balance):
balance = [initial_balance]
def deposit(amount):
balance[0] = balance[0] + amount
return balance
def withdraw(amount):
balance[0] = balance[0] - amount
return balance
return deposit, withdraw
Support for rebinding in nested scopes would make this code
clearer. A class that defines deposit() and withdraw() methods
and the balance as an instance variable would be clearer still.
Since classes seem to achieve the same effect in a more
straightforward manner, they are preferred.
Implementation
An implementation effort is underway. The implementation requires
a way to create closures, an object that combines a function's
code and the environment in which to resolve free variables.
There are a variety of implementation alternatives for closures.
One possibility is to use a static link from a nested function to
its enclosing environment. This implementation requires several
links to be followed if there is more than one level of nesting
and keeps many garbage objects alive longer than necessary.
One fairly simple implementation approach would be to implement
the default argument hack currently used for lambda support. Each
function object would have a func_env slot that holds a tuple of
free variable bindings. The code inside the function would use
LOAD_ENV to access these bindings rather than the typical
LOAD_FAST.
The problem with this approach is that rebindings are not visible
to the nested function. Consider the following example:
import threading
import time
def outer():
x = 2
def inner():
while 1:
print x
time.sleep(1)
threading.Thread(target=inner).start()
while 1:
x = x + 1
time.sleep(0.8)
If the func_env slot is defined when MAKE_FUNCTION is called, then
x in innner() is bound to the value of x in outer() at function
definition time. This is the default argument hack, but not
actual name resolution based on statically nested scopes.
Local Variables: