tags:

views:

313

answers:

2

This is some code I found on the internet. I'm not sure how it is meant to be used. I simply filled members with the enum keys/values and it works, but I'm curious what this metaclass is all about. I am assuming it has something to do with ctypes, but I can't find much information on subclassing ctypes. I know EnumerationType isn't doing anything the way I'm using Enumeration.

from ctypes import *

class EnumerationType(type(c_uint)):  
    def __new__(metacls, name, bases, dict):  
        if not "_members_" in dict:  
            _members_ = {}  
            for key,value in dict.items():  
                if not key.startswith("_"):  
                    _members_[key] = value  
            dict["_members_"] = _members_  
        cls = type(c_uint).__new__(metacls, name, bases, dict)  
        for key,value in cls._members_.items():  
            globals()[key] = value  
        return cls  

    def __contains__(self, value):
        return value in self._members_.values()

    def __repr__(self):
        return "<Enumeration %s>" % self.__name__

class Enumeration(c_uint):
    __metaclass__ = EnumerationType
    _members_ = {}
    def __init__(self, value):
        for k,v in self._members_.items():
            if v == value:
                self.name = k
                break
        else:
            raise ValueError("No enumeration member with value %r" % value)
        c_uint.__init__(self, value)


    @classmethod
    def from_param(cls, param):
        if isinstance(param, Enumeration):
            if param.__class__ != cls:
                raise ValueError("Cannot mix enumeration members")
            else:
                return param
        else:
            return cls(param)

    def __repr__(self):
        return "<member %s=%d of %r>" % (self.name, self.value, self.__class__)

And an enumeration probably done the wrong way.  

class TOKEN(Enumeration):
    _members_ = {'T_UNDEF':0, 'T_NAME':1, 'T_NUMBER':2, 'T_STRING':3, 'T_OPERATOR':4, 'T_VARIABLE':5, 'T_FUNCTION':6}
+3  A: 

A metaclass is a class used to create classes. Think of it this way: all objects have a class, a class is also an object, therefore, it makes sense that a class can have a class.

http://www.ibm.com/developerworks/linux/library/l-pymeta.html

To understand what this is doing, you can look at a few points in the code.

 _members_ = {'T_UNDEF':0, 'T_NAME':1, 'T_NUMBER':2, 'T_STRING':3, 'T_OPERATOR':4, 'T_VARIABLE':5, 'T_FUNCTION':6}

globals()[key] = value

Here it takes every defined key in your dictionary: "T_UNDEF" "T_NUMBER" and makes them available in your globals dictionary.

def __init__(self, value):
    for k,v in self._members_.items():
        if v == value:
            self.name = k
            break

Whenever you make an instance of your enum, it will check to see if the "value" is in your list of allowable enum names when you initialized the class. When the value is found, it sets the string name to self.name.

c_uint.__init__(self, value)

This is the actual line which sets the "ctypes value" to an actual c unsigned integer.

Unknown
Thanks. That article explained everything. I guess you learn something new every day.
Scott
+1  A: 

That is indeed a weird class.

The way you are using it is correct, although another way would be:

class TOKEN(Enumeration):
    T_UNDEF    = 0
    T_NAME     = 1
    T_NUMBER   = 2
    T_STRING   = 3
    T_OPERATOR = 4
    T_VARIABLE = 5
    T_FUNCTION = 6

(That's what the first 6 lines in __new__ are for)

Then you can use it like so:

>>> TOKEN
<Enumeration TOKEN>
>>> TOKEN(T_NAME)
<member T_NAME=1 of <Enumeration TOKEN>>
>>> T_NAME in TOKEN
True
>>> TOKEN(1).name
'T_NAME'

The from_param method seems to be for convenience, for writing methods that accept either an int or an Enumeration object. Not really sure if that's really its purpose.

I think this class is meant to be used when working with external APIs the use c-style enums, but it looks like a whole lot of work for very little gain.

itsadok
Well, it gained me a fast expression evaluator with a bunch of Linux related plugins for my LCD project. :) The ctypes module doesn't explicitly provide a way to represent enums, hence the code. The same can be done without the metaclass I found. That's just there to provide the globals. I've been using Python for over 5 years and I never knew about metaclasses. I thought it was ctypes specific when I saw it.
Scott