1

My previous question asked about Enum in python

Lets say I am creating State classes:

class State(object):
    """
    Abstract class representing a state.
    """
    enum = 1

    def __init__(self, instance):
        self.instance = instance

    def is_flag(self, flag):
        return flag & self.enum == self.enum

And then I create different actual States

class New(State):
    enum = 2
    def pay(self):
        ...

class Paid(State):
    enum = 4
    def complete(self):
        ...

class Completed(State):
    enum = 8


ACCOUNTABLE = Paid.enum | Completed.enum

while this works, I would like to automate the generation of the enum values, and it seems like it can be done by using Meta classes, the question is how?

4
  • Abstract base classes aren't the same thing a metaclasses. Regardless, creating separate classes with an attribute holding a single integer value seems like an awfully heavy-weight approach to creating Enum values. Another issue with this approach is that the integer values generated could vary depending on the order and number of of subclasses defined -- which means they wouldn't be constant values, something which is often desirable for enumerations. Commented Aug 28, 2014 at 4:10
  • I have also considered the inconsistent values can be generated by the metaclass. I want to implement StateMachine pattern not just the Enum. Commented Aug 28, 2014 at 20:48
  • It's unclear how the StateMachine pattern would make it possible to avoid the issue of inconsistent values. Also your use of ABCMeta is unnecessary, both here and in the answer you posted to your own question. In Python abstract base classes aren't the same thing as they are in, say, C++. If you insist on using classes to represent different Enum values -- a questionable approach -- a metaclass is probably all you'd need. Commented Aug 28, 2014 at 21:02
  • OK thanks, I have removed the ABCMeta, I thought of using that ABCMeta could stop accidentally instating State class. I have also noticed the possible inconsistent enum values being generated, so in my actual implementation I have commented out the metaclass and specify the enum explicitly. Commented Aug 28, 2014 at 21:53

3 Answers 3

4

Python 3.4 has an Enum data type, which has been backported.

from enum import IntEnum

States = IntEnum('States', [(n, 2**i) for i, n in enumerate('New Paid Complete'.split(), 1)])

list(States) # [<States.New: 2>, <States.Paid: 4>, <States.Complete: 8>]

or

class States(IntEnum):
    New = 2
    Paid = 4
    Complete = 8
    def is_flag(self, flag):
        return self & flag == flag
Sign up to request clarification or add additional context in comments.

Comments

1

While I don't think defining a separate class for each enum value is a very robust approach for the reasons stated in my comments, here's one way it could be done which will support the creation of multiple independent base state classes.

class MetaState(type):
    _baseclasses = {}  # registry of instances & number of subclasses of each
    def __new__(cls, name, bases, attrs):
        cls = super(MetaState, cls).__new__(cls, name, bases, attrs)
        if bases == (object,):  # definition of a base class?
            MetaState._baseclasses[cls] = 0  # create initial registry entry
        else:  # must be derived from a previously defined base state class
            for base in bases:  # find base state class
                if base in MetaState._baseclasses:
                    setattr(cls, 'enum', 2 ** MetaState._baseclasses[base])
                    MetaState._baseclasses[base] += 1
                    break
            else:
                raise TypeError('Class not derived from base state class')
        return cls

class BaseState(object):
    """ Abstract base class for each derived state subclass. """
    __metaclass__ = MetaState

    def is_flag(self, flag):
        return flag & self.enum == self.enum

class A(BaseState): pass
class B(BaseState): pass
class C(BaseState): pass

print A.enum  # -> 1
print B.enum  # -> 2
print C.enum  # -> 4

Comments

0

I have also come up a solution (needs fixing) for my answer:

class StateMeta(type):

    def __new__(cls, name, bases, attrs):
        cls = super(StateMeta, cls).__new__(cls, name, bases, attrs)
        if bases[-1] == object:
            return cls

        setattr(cls, 'enum', 2 ** (len(bases[-1].__subclasses__())-1))
        return cls


class State(object):
    """
    Abstract class representing a state.
    """
    __metaclass__ = StateMeta

    enum = 0

    def __init__(self, instance):
        self.instance = instance

    def is_flag(self, flag):
        return flag & self.enum == self.enum

Running it:

>>> class A(State):
...     pass
... 
>>> A.enum
1
>>> class B(State):
...     pass
... 
>>> B.enum
2

4 Comments

Why are you skipping an enum value of 1 (2**0)?
I am not skipping in purpose, but I cannot use the power of the iterator value because it's generating the enum value in a single class scope.
StateMeta.__new__() is called when class State is defined, as well as when classes A and B are (because the latter two inherit State's metaclass), so I don't understand what you mean by a "single class scope".
I guess you are right, when creating A class, the count of subclass of State should be 1 (before B gets created), so I can possibly use use 2**(len(_cls.__subclasses__())-1) to create enum value.

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.