0

I am trying to create an enumeration in python. I have seen seen several solutions (The second answer here by @alec thomas intrigued me most), but I would like to make the enumeration immutable. I found a python recipe that is immutable, but I want to have a dict-like key/value association.

I was attempting to use duck-punching to add properties to the class that would throw an AttributeError if you tried to call fset or fdel on the property.

I ran into trouble defining the fget function of the property. Here's the code i have so far:

def enum(*sequential, **named):
    # Build property dict
    enums = dict(zip(sequential, range(len(sequential))), **named)

    # Define an errorhandler function
    def err_func(*args, **kwargs):
        raise AttributeError('Enumeration is immutable!')

    # Create a base type
    t = type('enum', (object,), {})

    # Add properties to class by duck-punching
    for attr, val in enums.iteritems():
        setattr(t, attr, property(lambda attr: enums[attr], err_func, err_func))

    # Return an instance of the new class
    return t()

e = enum('OK', 'CANCEL', 'QUIT')
print e
print e.OK
print e.CANCEL
print e.QUIT

# Immutable?
e.OK = 'ASDF'  # Does throw the correct exception
print e.OK

The output from this is:

<__main__.enum object at 0x01FC8F70>
Traceback (most recent call last):
  File "enum.py", line 24, in <module>
    print e.OK
  File "enum.py", line 17, in <lambda>
    setattr(t, attr, property(lambda attr: enums[attr], err_func, err_func))
KeyError: <__main__.enum object at 0x01FC8F70>

Perhaps this is not the best way to create an enumeration, but it's short and I wanted to explore more of this whole duck-punching/monkey-patching concept.

Community
  • 1
  • 1
Josh
  • 1,046
  • 1
  • 17
  • 26

2 Answers2

1

Your immediate problem is that the getter of a property takes self as the only parameter, not attr. Therefore, you should use something like lambda self: val instead.

However, that doesn't work because the lambda binds the name val, which changes from iteration to iteration. So you will need to wrap it somehow:

def getter(val):
    return lambda self: val

for attr, val in enums.iteritems():
    setattr(t, attr, property(getter(val), err_func, err_func))
nneonneo
  • 154,210
  • 32
  • 267
  • 343
  • Thanks! The final solution looks like: `def getter(cls, val): return lambda cls: val`. The properties are inserted like this: `setattr(t, attr, property(getter(t, val), err_func, err_func))` – Josh Oct 18 '12 at 18:54
0

The final implementation (thanks to @nneonneo).

  • Checks for duplicate enum keys
  • Checks if enum is empty
  • Does not allow deletion or modification of enum items

    def enum(*sequential, **named):
        # Check for duplicate keys
        names = list(sequential)
        names.extend(named.keys())
        if len(set(names)) != len(names):
            raise KeyError('Cannot create enumeration with duplicate keys!')
    
        # Build property dict
        enums = dict(zip(sequential, range(len(sequential))), **named)
        if not enums:
            raise KeyError('Cannot create empty enumeration')
    
        # Function to be called as fset/fdel
        def err_func(*args, **kwargs):
            raise AttributeError('Enumeration is immutable!')
    
        # function to be called as fget
        def getter(cls, val):
            return lambda cls: val
    
        # Create a base type
        t = type('enum', (object,), {})
    
        # Add properties to class by duck-punching
        for attr, val in enums.iteritems():
            setattr(t, attr, property(getter(t, val), err_func, err_func))
    
        # Return an instance of the new class
        return t()
    
Josh
  • 1,046
  • 1
  • 17
  • 26