Search code examples
pythonscopeclass-variables

Class variable in `for`...`if` expression fails in python


Take this simple code:

class A(object):
  numbers = [1, 2, 3]
  numberscopy = numbers[:]
  print(*(a for a in numberscopy))
  print(*(a for a in numberscopy if a in numbers))

I define the numbers variable inside the class. I can then use it to do other things, like make a copy, iterate over it, and print its contents.

But the last line, with the for-if statement, fails with NameError: global name 'numbers' is not defined. Not numberscopy, just numbers.

I tried on both python 2.7.14+ (with print_function imported) and 3.7.0, with the same result.

Why does this happen? Is it intended to work this way?


Solution

  • The code inside class-bodies is somewhat messed in Python. It is not so much a bug, but an "implementation problem that got weird".

    The problem being that: code usually runs at module level, or inside functions - when inside functions, there are well-defined "local" variables.

    Code inside class bodies also run with a "local" scope - but if one creates functions that are run while the class body is being processed, these do not "see" the outer-level variables. And generator expressions, as do comprehensions (in Python 3, Python 2 is another language, which is on its way out, let's not complicate stuff). The expression used to create the iterator to the for inside the generator is run in a scope where it "sees" the outer variables. The main expression and if expressions themselves are inside the generator, and can't "see" those variables.

    So, a workaround, if comprehensions are needed inside a class body is to have an intermediary function inside the class body, just to generate the values and variables you need, and have line to call that and update the class's own variables with the local namespace of that inner function:

    class A:
       def create_vals():
           numbers = [1, 2, 3]
           numbers_copy = numbers[:]
           values = list(a for a in numbers if a in numbers_copy)
           return locals()
       locals().update(create_vals())
       del create_vals
    

    So, inside the temporary create_vals function (it is not a method), usual scope-nesting rules apply - and with the last two lines we copy the created variables to the class itself, and remove the temporary function.