Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: Make a dynamically created class (as in the class itself) iterable?

Tags:

python

I started with this code snippet, which, by my understanding, is essentially a class-factory of sorts to emulate an "enum" type from other languages:

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

(which I took from here: How can I represent an 'Enum' in Python? )

I get how it works, and it does, but I would like to make my dynamically generated classes, which are of type 'Enum', iterable so that i can do the following in order to do a sanity check:

MyEnum = enum('FOO', 'BAR', 'JIMMY')
def func(my_enum_value):  # expects one of the MyEnum values
    if not my_enum_value in MyEnum:
        raise SomeSortOfException

However, in order to make that sanity check work, I need to make MyEnum iterable. I read here: http://pydanny.blogspot.com/2007/10/required-methods-to-make-class-iterable.html that I needed to add iter len contains and getitem methods to something in order to make it iterable. I started down this road (rewriting the enum code) but got stuck:

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    enums['_enums'] = enums.copy() # (so I'd have them in the Class in order to use for the methods I'll be implementing to make it iterable)
    Enum = type('Enum', (), enums)

    Enum.__iter__ = #Er, how do I do this? Guess I'll ask SO
    Enum.__len__ =  # etc. 
    # . . . 
    return Enum

SO, how do I make my generated Enum classes iterable so I can use the 'in' word with them? This isn't life or death for me, but I'm learning a ton of Python by doing this.

like image 629
B Robster Avatar asked Apr 23 '26 19:04

B Robster


2 Answers

Use a smarter metaclass than type.

class EnumMC(type):
  def __contains__(self, val):
    return val in self.__dict__

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

MyEnum = enum('FOO', 'BAR', 'JIMMY')
def func(my_enum_value):  # expects one of the MyEnum values
    if not my_enum_value in MyEnum:
        raise ValueError()

func('FOO')
func('QUUX')

You may want to use an actual attribute to store the enum keys rather than depending on the class dictionary though.

like image 91
Ignacio Vazquez-Abrams Avatar answered Apr 25 '26 08:04

Ignacio Vazquez-Abrams


class Enum(dict): pass
def enum(*sequential, **named):
    return Enum(zip(sequential, range(len(sequential))), **named)

This is much easier to read, and it supports iteration.

Edit: You still need to do

MyEnum = enum('FOO', 'BAR', 'JIMMY')

then you can do

'FOO' in MyEnum

You could also just use

class Enum(dict):
    def __init__(self, *sequential, **named):
        self.update(zip(sequential, range(len(sequential))))
        self.update(named)
def __getattr__(self, attr):
    if attr in self:
        return self[attr]
    else:
        return super(Enum, self).__getattr__(attr)

to do the same thing.

Edit 2: Another option.

from collections import namedtuple

def enum(*sequential):
    return namedtuple('Enum', sequential)(*sequential)

still call it the same way, and you can do MyEnum.FOO and it supports in. Also edited my above class to support attribute access.

You can also uses range(len(sequential)) to fill in the namedtuple values (not keys) if you want sequential integer values, or use **kwargs and then kwargs.keys() and kwargs.values() if you want to specify arbitrary values.

The last solution is cross-posted to How can I represent an 'Enum' in Python?

like image 42
agf Avatar answered Apr 25 '26 07:04

agf



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!