Динамически создавать статические переменные (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