5

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

inspectorG4dget
  • 110,290
  • 27
  • 149
  • 241
  • I just realized that this post might be mistaken for a duplicate of [how to create class variable dynamically in python](http://stackoverflow.com/q/8307612/198633). I was not able to find an answer there that I'm able to implement – inspectorG4dget Nov 12 '13 at 04:59
  • 1
    There's [enum34: Python 3.4 Enum backported](https://pypi.python.org/pypi/enum34) – falsetru Nov 12 '13 at 05:00
  • @falsetru: I'd appreciate it if you could post and example. From what I see of `enum34`, I quite like it – inspectorG4dget Nov 12 '13 at 05:02

5 Answers5

4

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.

Community
  • 1
  • 1
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
2

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
falsetru
  • 357,413
  • 63
  • 732
  • 636
  • The problem with this is that `Node` can't inherit from `State`. You could use `__getattr__` delegation or attribute-copying to put the values into the `Node` class, but that doesn't save too much over what he's already got. See the Restricted subclassing of enumerations section in the docs enum34 docs for the rationale. – abarnert Nov 12 '13 at 05:14
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
abarnert
  • 354,177
  • 51
  • 601
  • 671
  • Those values are still not accessible in `__init__`. How to fix that? – thefourtheye Nov 12 '13 at 10:57
  • 1
    @thefourtheye: they should be accessible as `self.state1`, `self.state2`, etc. – Ethan Furman Nov 12 '13 at 16:39
  • @thefourtheye: Unless you're doing some very tricky stuff to, e.g., build a singleton instance as part of the class definition, the class attributes will be available before you can initialize an instance. (If you were trying to access them as bare names, that _never_ works for class attributes. But as members of `self` or `Node`, they'll be there.) – abarnert Nov 12 '13 at 18:42
  • @EthanFurman If those are instance variables `print dir(Node)` should not show `state1` and `state2`, right? – thefourtheye Nov 13 '13 at 01:06
  • 1
    @thefourtheye: They're not instance variables, they're class variables. You can access class variables on `self`. You cannot access class variables as bare names. – abarnert Nov 13 '13 at 01:38
  • @abarnert Thanks :) I got it now. +1 – thefourtheye Nov 13 '13 at 01:42
1

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)
Wolph
  • 78,177
  • 11
  • 137
  • 148
1

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
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237