Weird scoping behavior in python

snickerdoodles777

Consider the following snippet of python code:

x = 1
class Foo:
    x = 2
    def foo():
        x = 3
        class Foo:
            print(x) # prints 3
Foo.foo()

As expected, this prints 3. But, if we add a single line to the above snippet, the behavior changes:

x = 1
class Foo:
    x = 2
    def foo():
        x = 3
        class Foo:
            x += 10
            print(x) # prints 11
Foo.foo()

And, if we switch the order of the two lines in the above example, the result changes yet again:

x = 1
class Foo:
    x = 2
    def foo():
        x = 3
        class Foo:
            print(x) # prints 1
            x += 10
Foo.foo()

I'd like to understand why this occurs, and more generally, understand the scoping rules that cause this behavior. From the LEGB scoping rule, I would expect that both snippets print 3, 13, and 3, since there is an x defined in the enclosing function foo().

juanpa.arrivillaga

Class block scope is special. It is documented here:

A class definition is an executable statement that may use and define names. These references follow the normal rules for name resolution with an exception that unbound local variables are looked up in the global namespace. The namespace of the class definition becomes the attribute dictionary of the class. The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods – this includes comprehensions and generator expressions since they are implemented using a function scope.

Basically, class blocks do not "participate" in creating/using enclosing scopes.

So, it is actually the first example that isn't working as documented. I think this is an actual bug.

EDIT:

OK, so actually, here's some more relevant documentation from the data model, I think it all is actually consistent with the documentation:

The class body is executed (approximately) as exec(body, globals(), namespace). The key difference from a normal call to exec() is that lexical scoping allows the class body (including any methods) to reference names from the current and outer scopes when the class definition occurs inside a function.

So class blocks do participate in using enclosing scopes, but for free variables (as is normal anyway). In the first piece of documentation that I'm quoting, the part about "unbound local variables are looked up in the global namespace" applies to variables that would normally be marked local by the complier. So, consider this notorious error, for example:

x = 1
def foo():
    x += 1
    print(x)

foo()

Would throw an unbound local error, but an equivalent class definition:

x = 1
class Foo:
    x += 1
    print(x)

will print 2.

Basically, if there is an assignment statement anywhere in a class block, it is "local", but it will check in the global scope if there is an unbound local instead of throwing the UnboundLocal error.

Hence, in your first example, it isn't a local variable, it is simply a free variable, and the resolution goes through the normal rules. In your next two examples, you us an assignment statemnt, marking x as "local", and thus, it will be looked up in the global namespace in case it is unbound in the local one.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

Python datetime weird behavior

From Dev

python thread weird behavior

From Dev

python dictionary weird behavior

From Dev

Set weird behavior (python)

From Dev

python OOP class method retaining variable. Weird scoping thing

From Dev

JavaScript Class Weird Scoping

From Dev

Weird behavior in Python in array initialization

From Dev

Python, weird memory consumption behavior

From Dev

Weird behavior in python list concatenation

From Dev

The weird behavior of the print function in python

From Dev

Weird python file path behavior

From Dev

Weird sets behavior in python REPL

From Dev

Weird behavior with a lot of numbers python

From Dev

python dictionary weird behavior with dict as container

From Dev

Weird Behavior When Slicing a List in Python

From Dev

Python Flask weird logging behavior (kubernetes)

From Dev

Python: Weird behavior while using `yield from`

From Dev

Knapsack algorithm, weird behavior (python3)

From Dev

Python getting ranks of elements in list weird behavior

From Dev

Weird behavior of barplot from python matplotlib with datetime

From Dev

multiprocessing.Queue weird python behavior

From Dev

Decimal Python Library has weird behavior

From Dev

Weird behavior of (^)

From

Strange variable scoping behavior in Jenkinsfile

From Dev

Weird scoping issue with method parameters and "using" statement

From Dev

Python Class & Class Instantiation shows a very weird behavior

From Dev

Template / Generic user-defined classes in python weird behavior

From Dev

python3: weird behavior when pattern matching with regex

From Dev

Python/Bottle template weird behavior when generating html table

Related Related

HotTag

Archive