Динамически создавать статические переменные (Enum hack)

Я пытаюсь создать набор состояний для Node учебный класс. Обычно я бы сделал это, установив каждый Node экземпляра state переменная к intи документ, который int соответствует какому состоянию (так как у меня нет enumс).
На этот раз я хотел бы попробовать что-то другое, поэтому я решил пойти с этим:

class Node:
  state1 = 1
  state2 = 2

  def __init__(self):
    ...

Это хорошо работает. Тем не менее, я столкнулся с проблемой, когда у меня много состояний - слишком много, чтобы печатать вручную. Кроме того, с таким количеством состояний я могу сделать ошибку и назначить int в двух штатах. Это может стать источником ошибок при тестировании состояний (например: if self.state==Node.state1 может потерпеть неудачу, если Node.state1 а также Node.state2 были оба 3).

По этой причине я хотел бы сделать что-то вроде этого:

class Node:
  def __init__(self):
    ...
...

for i,state in enumerate("state1 state2".split()):
  setattr(Node, state, i)

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

Есть ли способ, которым я мог бы установить переменные класса в определении класса таким образом? В идеале я хотел бы сделать это:

class Node:
  for i,state in enumerate("state1 state2".split()):
    setattr(Node, state, i)

... но это не сработает Node еще не был определен, и приведет к NameError

Или сделать enumсуществуют в python3.3?

Я на Python3.3.2, если это имеет значение

5 ответов

Решение

Если ваша единственная проблема с выполнением setattr после определения класса, что это некрасиво и не в том месте, как насчет использования декоратора для этого?

def add_constants(names):
    def adder(cls):
        for i, name in enumerate(names):
            setattr(cls, name, i)
        return cls
    return adder

@add_constants("state1 state2".split())
class Node:
    pass

Теперь, когда у Python есть Enum типа, нет причин не использовать его. С таким количеством состояний я бы предложил использовать документированный AutoEnum. Что-то вроде этого:

class Node:

    class State(AutoEnum):
        state1 = "initial state before blah"
        state2 = "after frobbing the glitz"
        state3 = "still needs the spam"
        state4 = "now good to go"
        state5 = "gone and went"

    vars().update(State.__members__)

Тогда в использовании:

--> Node.state2
<State.state2: 2>

Примечание: рецепт связан с Python 2.x - вам нужно удалить unicode ссылка, чтобы заставить его работать в Python 3.x.

Есть enum34: Python 3.4 Enum поддерживается

>>> import enum
>>> State = enum.IntEnum('State', 'state1 state2')
>>> State.state1
<State.state1: 1>
>>> State.state2
<State.state2: 2>
>>> int(State.state2)
2

С помощью AutoNumber из документации перечисления Python 3.4:

>>> import enum
>>> class AutoNumber(enum.Enum):
...     def __new__(cls):
...         value = len(cls.__members__) + 1
...         obj = object.__new__(cls)
...         obj._value_ = value
...         return obj
... 
>>> class Node(AutoNumber):
...     state1 = ()
...     state2 = ()
... 
>>> Node.state1
<Node.state1: 1>
>>> Node.state2
<Node.state2: 2>
>>> Node.state2.value
2

Есть несколько способов сделать это, я бы сказал, что наиболее очевидный из них - использование метакласса, но также подойдет повышение уровня отступа для цикла 1.

Что касается существования перечислений: http://docs.python.org/3.4/library/enum.html

Вот пример метакласса:

class EnumMeta(type):
    def __new__(cls, name, bases, dict_):
        names = dict_.get('_enum_string', '')
        if names:
            for i, name in enumerate(names.split()):
                dict_[name] = 'foo %d' % i

        return super(EnumMeta, cls).__new__(cls, name, bases, dict_)


class Node(object):
    __metaclass__ = EnumMeta
    _enum_string = 'state1 state2'

print 'state1', SomeMeta.state1
print 'state2', SomeMeta.state2

Или простая версия с циклом (но некрасиво и менее гибкая):

class Node(object):
    pass

for i, state in enumerate('state1 state2'.split()):
    setattr(Node, state, i)

Есть ли способ, которым я мог бы установить переменные класса в определении класса таким образом? В идеале я хотел бы сделать это:

class Node:
    for i,state in enumerate("state1 state2".split()):
        setattr(Node, state, i)

... но это не сработает, поскольку Node еще не определен, и приведет к ошибке NameError

Хотя класс еще не существует, используемое им пространство имен существует. Это может быть доступно с vars() (а также, я думаю, locals()). Это означает, что вы можете сделать что-то вроде:

class Node:
    node_namespace = vars()
    for i, state in enumerate('state1 state2'.split()):
        node_namespace[state] = i
    del node_namespace
Другие вопросы по тегам