Initializing one static data member from another

Why does this work,

class Good:
    A = [1, 2, 3]
    B = [x for x in A]

but this raises NameError on the last line, saying Z is undefined?

class NotSoGood:
    Z = 2

    A = [1, 2, 3]
    B = [x for x in A if x != Z]

Is there a way to get the same effect, but keep all three as static data members of this class? I know I can move Z outside the class, for example.



1 Like

This is a bit of a strange wrinkle IMO. Here’s a StackOverflow post that explains it:

1 Like

That’s a good find, and the PEP 227 quote explains it, repeated here.

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.

Even though the behavior is still surprising to me, the tradeoff in the PEP sounds like the right one. Otherwise, there would be even more surprises, some quiet.


On the other hand, that means referring to a class member in a list comprehension might or might not work, depending on where it is in the comprehension. If the reference is in the expression part of the for-clause, it’ll work; if it’s anywhere else, it’ll fail.

class NotSoGood:
    Z = 2
    A = [1, 2, 3]
    B = [x for x in A]                    # this works; A is in the for-clause
    C = [A[i] for i in range(3)]          # NameError: name 'A' is not defined
    D = [x for x in [1, 2, 3] if x != Z]  # NameError: name 'Z' is not defined

So I wonder why the rule can’t be extended to also evaluate the optional if-clause to include names in class scope. Then, that last line would work, as would my original problem. The tradeoff that led to the current behavior seems unaffected by such a change.

The reason is more obvious if you switch those to a generator expression - in that case it’s possible expressions in the various clauses might outlive the class body. In the case of a list comprehension that isn’t possible, but it’s intentional that they share the same rules. The “topmost” for clause is executed initially, and is actually passed into the function implementing comprehensions which is why it works.

It could be possible to do it just for comprehensions, but then list(... for x in y) would no longer be equivalent to [... for x in y] which is quite undesirable and confusing.

I don’t see the harm in a generator outliving the class body.

class Contrived:
    A = [1, 2, 3]
    G = (x for x in A)

list(Contrived.G)    # [1, 2, 3]
list(Contrived.G)    # []

Maybe that’s not what you mean by “outlive”. The closest reason I can find for limiting member references to the for-clause is this consistency argument from the PEP, and it’s reasonable.

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.

I think this would be an example of that obscurity.

class Obscure:
    x = 42
    y = [x for x in range(10)]  # hmm, I hope this doesn't change Obscure.x, etc.