【问题标题】:Python metaclass to generate correct enum values for classPython元类为类生成正确的枚举值
【发布时间】:2014-10-21 17:11:02
【问题描述】:

我的previous question 询问了python中的枚举

假设我正在创建状态类:

class State(object):
    """
    Abstract class representing a state.
    """
    enum = 1

    def __init__(self, instance):
        self.instance = instance

    def is_flag(self, flag):
        return flag & self.enum == self.enum

然后我创建不同的实际状态

class New(State):
    enum = 2
    def pay(self):
        ...

class Paid(State):
    enum = 4
    def complete(self):
        ...

class Completed(State):
    enum = 8


ACCOUNTABLE = Paid.enum | Completed.enum

虽然这可行,但我想自动生成枚举值,而且似乎可以通过使用 Meta 类来完成,问题是如何?

【问题讨论】:

  • 抽象基类与元类不同。无论如何,使用包含单个整数值的属性创建单独的类似乎是创建 Enum 值的一种非常重量级的方法。这种方法的另一个问题是,生成的整数值可能会根据定义的子类的顺序和数量而有所不同——这意味着它们不会是常量值,而这通常是枚举所需要的。
  • 我也考虑过元类可以生成不一致的值。我想实现 StateMachine 模式而不仅仅是枚举。
  • 目前还不清楚 StateMachine 模式如何能够避免值不一致的问题。此外,您无需使用ABCMeta,无论是在此处还是在您发布给您自己的问题的答案中。 Python 中的抽象基类与 C++ 中的抽象基类不同。如果您坚持使用类来表示不同的 Enum 值——这是一种有问题的方法——可能只需要一个元类。
  • 好的,谢谢,我已经删除了 ABCMeta,我想使用 ABCMeta 可以停止意外地设置 State 类。我还注意到生成的枚举值可能不一致,所以在我的实际实现中,我已经注释掉了元类并明确指定了枚举。

标签: python enums metaclass


【解决方案1】:

Python 3.4 有一个Enum data type,其中has been backported

from enum import IntEnum

States = IntEnum('States', [(n, 2**i) for i, n in enumerate('New Paid Complete'.split(), 1)])

list(States) # [<States.New: 2>, <States.Paid: 4>, <States.Complete: 8>]

class States(IntEnum):
    New = 2
    Paid = 4
    Complete = 8
    def is_flag(self, flag):
        return self & flag == flag

【讨论】:

    【解决方案2】:

    我还为我的回答提出了一个解决方案(需要修复):

    class StateMeta(type):
    
        def __new__(cls, name, bases, attrs):
            cls = super(StateMeta, cls).__new__(cls, name, bases, attrs)
            if bases[-1] == object:
                return cls
    
            setattr(cls, 'enum', 2 ** (len(bases[-1].__subclasses__())-1))
            return cls
    
    
    class State(object):
        """
        Abstract class representing a state.
        """
        __metaclass__ = StateMeta
    
        enum = 0
    
        def __init__(self, instance):
            self.instance = instance
    
        def is_flag(self, flag):
            return flag & self.enum == self.enum
    

    运行它:

    >>> class A(State):
    ...     pass
    ... 
    >>> A.enum
    1
    >>> class B(State):
    ...     pass
    ... 
    >>> B.enum
    2
    

    【讨论】:

    • 为什么要跳过枚举值 1 (2**0)?
    • 我不是故意跳过,但我不能使用迭代器值的强大功能,因为它在单个类范围内生成枚举值。
    • StateMeta.__new__() 是在定义类State 时调用的,以及类AB 时调用(因为后两者继承State 的元类),所以我不明白您所说的“单类范围”是什么意思。
    • 我猜你是对的,在创建 A 类时,State 的子类计数应该是 1(在创建 B 之前),所以我可以使用 use 2**(len(_cls.__subclasses__ ())-1) 创建枚举值。
    【解决方案3】:

    虽然出于我的 cmets 中所述的原因,我不认为为每个枚举值定义一个单独的类是一种非常稳健的方法,但这是一种可以支持创建多个独立基状态类的方法。

    class MetaState(type):
        _baseclasses = {}  # registry of instances & number of subclasses of each
        def __new__(cls, name, bases, attrs):
            cls = super(MetaState, cls).__new__(cls, name, bases, attrs)
            if bases == (object,):  # definition of a base class?
                MetaState._baseclasses[cls] = 0  # create initial registry entry
            else:  # must be derived from a previously defined base state class
                for base in bases:  # find base state class
                    if base in MetaState._baseclasses:
                        setattr(cls, 'enum', 2 ** MetaState._baseclasses[base])
                        MetaState._baseclasses[base] += 1
                        break
                else:
                    raise TypeError('Class not derived from base state class')
            return cls
    
    class BaseState(object):
        """ Abstract base class for each derived state subclass. """
        __metaclass__ = MetaState
    
        def is_flag(self, flag):
            return flag & self.enum == self.enum
    
    class A(BaseState): pass
    class B(BaseState): pass
    class C(BaseState): pass
    
    print A.enum  # -> 1
    print B.enum  # -> 2
    print C.enum  # -> 4
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多