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

3

5 Answers 5

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.

Sign up to request clarification or add additional context in comments.

Comments

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

5 Comments

Those values are still not accessible in __init__. How to fix that?
@thefourtheye: they should be accessible as self.state1, self.state2, etc.
@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.)
@EthanFurman If those are instance variables print dir(Node) should not show state1 and state2, right?
@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.
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

1 Comment

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

7 Comments

Are the enums from 3.4 future-importable?
Would you care to share some of these multiple ways of doing this, so that I could try them out?
is this what you are referring to? If so, how can I modify it for my needs?
Didn't notice the 3.3 versus 3.4 issue with the enum class, it might be possible to run the enum.py file directly but there might be dependencies that are not 3.3 compatible (get it here: hg.python.org/cpython/file/default/Lib/enum.py )
pip install enum34, then just import enum34 instead of import enum and you can use the same code in 3.3.
|
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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.