2

I am using Python 2.7 and I am trying to define an enum class

from test_module import BannerType
class LoginBannerType:
    Login_Banner_Flag = 1 << 4
    VALUE_TO_NAME = dict(
        (k, v) for k, v in BannerType.VALUE_TO_NAME.items() if (k & Login_Banner_Flag))

Here Login_Banner_Flag refers to the class-level attribute I just defined but when I try import this enum class, I got this error:

(k, v) for k, v in BannerType.VALUE_TO_NAME.items() if (k & Login_Banner_Flag))
NameError: global name 'Login_Banner_Flag' is not defined

I tried to change that line to the following but it does not work either:

VALUE_TO_NAME = dict(
        (k, v) for k, v in BannerType.VALUE_TO_NAME.items() if (k & LoginBannerType.Login_Banner_Flag))

So it is a comprehension and I am supposed to get reference to outer variables right? What is wrong with my code and how to fix this?

Junchao Gu
  • 1,815
  • 6
  • 27
  • 43

2 Answers2

3

The problem is that you are using a generator, which has its own function scope. Moreover a class doesn't have a scope until it's definition is completely. Which means your generator simply cannot reference Login_Banner_Flag.

This is documented here:

A scope defines the visibility of a name within a block. If a local variable is defined in a block, its scope includes that block. If the definition occurs in a function block, the scope extends to any blocks contained within the defining one, unless a contained block introduces a different binding for the name. 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. This means that the following will fail:

class A:
    a = 42
    b = list(a + i for i in range(10))

When a name is used in a code block, it is resolved using the nearest enclosing scope. The set of all such scopes visible to a code block is called the block’s environment.

This is the same reason why the following fails:

class A:
    x = 1
    def a(self):
        print(x)   # you have to use A.x

A().a()   #error! x isn't defined

However in your case the problem is that you cannot even use LoginBannerType.Login_Banner_Flag because the class LoginBannerType doesn't exist until the end of the declaration and the generator requires that value before that point.

To solve this you can:

  1. Populate that dictionary after the class is created:

    from test_module import BannerType
    class LoginBannerType:
        Login_Banner_Flag = 1 << 4
        VALUE_TO_NAME = {}
    
        # etc
    
    LoginBannerType.VALUE_TO_NAME = dict(... #use LoginBannerType.Login_Banner_Flag)
    
  2. Use a decorator, which basically is like doing 1 but hiding it inside an @initialize_class

  3. Use the default argument hack to get the value from the class scope:

    from test_module import BannerType
    class LoginBannerType:
        Login_Banner_Flag = 1 << 4
        def f(flag=Login_Banner_Flag):
            return dict((k, v) for k, v in BannerType.VALUE_TO_NAME.items() if (k & flag))
        VALUE_TO_NAME = f()
        del f
    
Junchao Gu
  • 1,815
  • 6
  • 27
  • 43
Bakuriu
  • 98,325
  • 22
  • 197
  • 231
1

Both Login_Banner_Flag and VALUE_TO_NAME are class variables not visible to each other.

Inline Login_Banner_Flag:

(k & (1 << 4))