Python noobie scoping question

I wrote this code:

x = 0 def counter(): x = 1 def temp(self): print x x += 1 return temp 

Trying to check if python is a lexical or dynamic scope. I thought that

 y = counter() y() 

Should print 0 or 1 and that will tell me how python works. However, calling y raises an exception saying x is undefined. In my understanding of how Python works, something seems to be fundamentally wrong.

Can someone explain how this works? Yes, I know that this can be easily done using objects. I am trying to explore the idea of โ€‹โ€‹providing state functions without using objects. I wrote the code this way because the above, translated into a lexically restricted language such as Scheme, would definitely work.

+8
python scoping
source share
5 answers

From the docs :

Pythonโ€™s special quirk is this: if the global expression doesnโ€™t work, name assignments always go to the innermost scope. Jobs do not copy data โ€” they simply bind names to objects.

So when Python parses

  def temp(self): print x x += 1 

he sees the purpose of x += 1 and therefore decides that x should be in the innermost region. When you later call temp(...) via y() - (where, by the way, either self should be excluded from the temp definition, or y() should provide one argument) - Python encounters print x and finds that x not defined in local (innermost) area. Hence the error

 UnboundLocalError: local variable 'x' referenced before assignment 

If you announce

  def temp(self): global x 

then Python will look for x in the global scope (where x=0 ). In Python2, there is no way to tell Python to search for x in the extended area (where x=1 ). But in Python3 that can be achieved with an ad

  def temp(self): nonlocal x 
+10
source share

Python has two areas (actually three), plus special rules for local local areas. These two areas are global, for module names, and local, for anything in a function. Everything that you assign to a function is automatically local, unless you declare it otherwise with the global statement in that function. If you use a name that is not assigned anywhere in the function, this is not a local name; Python will (lexically) look for nesting functions to find out if they have that name as a local name. If there are no nested functions or the name is not local in any of them, this name is considered global.

(The global namespace is also special because it is in fact both a global module namespace and an embedded namespace that is hidden in the builtins or __builtins__ module.)

In your case, you have three x variables: one in the module area (global), one in the counter function and one in the temp function, because += also an assignment operator, since the fact of the name assignment makes it local to the function, your operator += will try to use a local variable that has not yet been assigned, and this will raise an UnboundLocalError.

If you pointed to all three of these references x to refer to a global variable, you need to make global x both in the counter and temp functions. In Python 3.x (but not 2.x), there is a nonlocal declaration similar to global , which you can use to make temp variable assignment to counter , but leave global x alone.

+6
source share

Python closures cannot be written, so you cannot write your code this way. If you are only reading a variable inside functions, you should be good. In Python 3, you can use the nonlocal keyword to get the behavior you want.

If you are using Python 2.5 or later, you can also use the yield keyword to write the above code as a generator.

+2
source share

The Python documentation will answer the question in detail: http://docs.python.org/reference/executionmodel.html#naming-and-binding

Thus, Python has static checking rules. If the function f determines or removes the variable name, then the variable name refers to the variable in the closure of the function f. If the function f uses only the name of the variable (without definition or deletion), then the name refers to what the name means in the parent scope. Continue through parent areas until a definition is found or you reach a global area. For example:

 def f1(x): def g(y): z.foo = y # assigns global z g(1) def f2(x): def g(y): x.foo = y # assigns f2 variable, because f2 defines xg(1) def f3(x): def g(y): x = C() x.foo = y # assigns g variable, because g defines x g(1) 

The global and (in Python 3) nonlocal keywords override the default nonlocal rules.

Although names are resolved statically, values โ€‹โ€‹are resolved dynamically. The value of a variable comes from the most recent definition or deletion of this variable during access to the variable. Values โ€‹โ€‹are displayed in closing the function where the variable is located.

+2
source share

Thanks for all your answers. Just in case, if someone cares, I kind of came up with a workaround for this problem. I did this by creating a scoper function to set the counter variable.

 >>> def gc(): ... def scoper(): ... scoper.s = 0 ... def rtn(): ... scoper.s += 1 ... return scoper.s ... return rtn ... return scoper() 

This allowed me to do this, which mimics the correct closures:

 >>> a = gc() >>> a() 1 >>> a() 2 >>> a() 3 >>> b = gc() >>> b() 1 >>> a() 4 >>> b() 2 
+1
source share

All Articles