2

After this answer by Alec Thomas, I am using the following to create enumerations:

def enum(*sequential):
    enums = dict(zip(sequential, range(len(sequential))))
    return type('Enum', (), enums)

I would like to be able to obtain the length of one of these enums. For example, I can write

>>> Suit = enum('spades', 'hearts', 'diamonds', 'clubs')
>>> Suit.spades
0
>>> Suit.hearts
1
>>> Suit.diamonds
2
>>> Suit.clubs
3

but there’s no way to list all of the enumeration values at runtime. I’d like to be able to do something like

>>> [s for s in Suit]
[0, 1, 2, 3]

I’ve tried assigning enum['__iter__'] within the enum() function, but I don’t know what kind of object I need to assign:

def enum(*sequential):
    enums = dict(zip(sequential, range(len(sequential))))
    enums['__iter__'] = iter(range(len(sequential)))
    return type('Enum', (), enums)

gives

>>> [s for s in Suit]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'type' object is not iterable

How can I give an enum the ability to list its members? (Even just the ability for the enum to report its length would suffice, since then the members are simply the elements of range(len(Suit)).)

Community
  • 1
  • 1
bdesham
  • 15,430
  • 13
  • 79
  • 123

4 Answers4

7

The problem is that type(…) returns a type, i.e. something that’s actually used to create objects. Now of course you could argue if that’s a problem or not—and it’s likely not due to how Python’s typing system works (everything is an object, types are just objects of type objects etc.).

The effect however is that you can’t add special methods, like __iter__ which would be needed in this case. Special methods are however looked up on the type of the object, so in this case on type (which is the base type of the type you’re creating). And type—as you would expect—is not iterable.

So if you want to have something iterable, you will need to create something that is not a type. You could probably come up with some fancy meta class here, but the easiest thing you can do, which also keeps your code at the same length, is really a named tuple. This is btw. also what the semantics in the Python 3.4 enum type is inspired by.

>>> def enum(*keys):
        return namedtuple('Enum', keys)(*range(len(keys)))
>>> Suit = enum('spades', 'hearts', 'diamonds', 'clubs')
>>> Suit.spades
0
>>> Suit.hearts
1
>>> Suit.diamonds
2
>>> Suit.clubs
3
>>> [s for s in Suit]
[0, 1, 2, 3]
poke
  • 369,085
  • 72
  • 557
  • 602
4

I would suggest you to use a different implementation of your enumerator by using collections.namedtuple

Implementation

>>> from collections import namedtuple
>>> def enum(*sequential):
    enums = namedtuple('enums',sequential)(*range(len(sequential)))
    return enums

Usage

>>> Suit = enum('spades', 'hearts', 'diamonds', 'clubs')
>>> Suit.spades
0
>>> Suit.hearts
1
>>> Suit.diamonds
2
>>> Suit.clubs
3
>>> [s for s in Suit]
[0, 1, 2, 3]
Abhijit
  • 62,056
  • 18
  • 131
  • 204
3

This seems to meet your requirements:

def enum(*sequential):
    length = len(sequential)
    pairs = zip(sequential, xrange(len(sequential)))
    Enum = type('Enum', (), dict(pairs))
    Enum.__len__ = lambda _: length
    Enum.__iter__ = lambda _: iter(range(length))
    return Enum()

Suit = enum('spades', 'hearts', 'diamonds', 'clubs')

print Suit.spades              # 0
print Suit.hearts              # 1
print Suit.diamonds            # 2
print Suit.clubs               # 3
print 'len(Suit):', len(Suit)  # len(Suit): 4
print [s for s in Suit]        # [0, 1, 2, 3]
martineau
  • 119,623
  • 25
  • 170
  • 301
1

Your variant works! with added () after type(...) to create class instance and modified __iter__:

def enum(*sequential):
    enums = dict(zip(sequential, range(len(sequential))))
    enums['__iter__'] = lambda e,i=iter(range(len(sequential))): i
    return type('Enum', (object,), enums)()

>>> suit = enum('spades', 'hearts', 'diamonds', 'clubs')
>>> [s for s in suit]
[0, 1, 2, 3]
ndpu
  • 22,225
  • 6
  • 54
  • 69