Question

I'm trying to create a set of states for a Node class. Normally, I would do this by setting each Node instance's state variable to an int, and document which int corresponds to which state (since I don't have enums).
This time, I'd like to try something different, so I decided to go with this:

class Node:
  state1 = 1
  state2 = 2

  def __init__(self):
    ...

This works well. However, I run into a problem where I have a LOT of states - too many to manually type out. Further, with that many states, I might make an error and assign the same int to two states. This would be a source of bugs when testing for states (e.g.: if self.state==Node.state1 might fail if Node.state1 and Node.state2 were both 3).

For this reason, I would like to do something like this:

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

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

While this would fix human errors in assigning values to states, it's quite ugly, as class variables are being set outside the class definition.

Is there a way I could set class variables within the class definition in this manner? I would ideally like to do this:

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

... but that won't work as Node hasn't been defined yet, and will result in a NameError

Alternatively, do enums exist in python3.3?

I'm on Python3.3.2, if it matters

Was it helpful?

Solution 2

If your only problem with doing the setattr after the class definition is that it's ugly and in the wrong place, what about using a decorator to do it?

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

OTHER TIPS

Now that Python has an Enum type, there's no reason not to use it. With so many states I would suggest using a documented AutoEnum. Something like this:

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__)

Then in usage:

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

Note: the recipe linked to is for Python 2.x -- you'll need to remove the unicode reference to make it work in Python 3.x.

There's enum34: Python 3.4 Enum backported

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

Using AutoNumber from Python 3.4 enum documentation:

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

There are multiple ways of doing this, I would say the most obvious one is using a metaclass but moving your for loop 1 indentation level up will also work.

As for the existance of enums: http://docs.python.org/3.4/library/enum.html

Here's a metaclass example:

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

Or a simple version with a loop (but ugly imho, and less flexible):

class Node(object):
    pass

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

Is there a way I could set class variables within the class definition in this manner? I would ideally like to do this:

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

... but that won't work as Node hasn't been defined yet, and will result in a NameError

While the class does not yet exist, the namespace it's using does. It can be accessed with vars() (and also, I think, locals()). This means you could do something like:

class Node:
    node_namespace = vars()
    for i, state in enumerate('state1 state2'.split()):
        node_namespace[state] = i
    del node_namespace
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top