2014-08-27 3 views
1

My previous question спросил о Enum в питона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, вопрос в том, как?

+0

Абстрактные базовые классы - это не одно и то же, что и метаклассы. Несмотря на это, создание отдельных классов с атрибутом, содержащим единственное целочисленное значение, похоже на ужасно тяжелый подход к созданию значений Enum. Другая проблема с этим подходом заключается в том, что генерируемые целочисленные значения могут варьироваться в зависимости от порядка и количества определенных подклассов, что означает, что они не будут постоянными значениями, что часто желательно для перечислений. – martineau

+0

Я также рассмотрел, что несогласованные значения могут быть сгенерированы метаклассом. Я хочу реализовать шаблон StateMachine, а не только Enum. –

+0

Непонятно, как шаблон StateMachine позволит избежать проблемы несогласованных значений. Кроме того, ваше использование 'ABCMeta' не нужно, как здесь, так и в ответе, который вы отправили на свой вопрос. В базовых классах Python абстрактные базовые классы - это не то же самое, что они, например, в C++. Если вы настаиваете на том, чтобы использовать классы для представления разных значений Enum - сомнительный подход - метакласс, вероятно, все, что вам нужно. – martineau

ответ

1

В то время как я не Не думаю, что определение отдельного класса для каждого значения перечисления является очень надежный подход по причинам, изложенным в моих комментариях, вот как это можно сделать, что будет способствовать созданию нескольких независимых классов базового состояния.

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 
3

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 
0

Я также придумать решение (потребности фиксации) для моего ответа:

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 
+0

Почему вы пропускаете значение перечисления 1 (2 ** 0)? – martineau

+0

Я не пропущен специально, но я не могу использовать значение итератора, потому что он генерирует значение перечисления в пределах одного класса. –

+0

'StateMeta .__ new __()' вызывается, когда определяется класс 'State', а также, когда классы' A' и 'B' (потому что последние два наследуют метакласс« State »), поэтому я не понять, что вы подразумеваете под «областью одного класса». – martineau