'How to define C-Enumeration types in python

I have an enumeration data type in C. How should I declare that in python-ctypes? I want this enum variable to be part of a structure and the assignment of the values to this structure would be done through memmove. After assigning, I want to display the values of each variables in the structure, and for the enum types I want to display the enum-string.



Solution 1:[1]

Antti Haapala did a fantastic job answering! I, however, did run into some minor issues when using it with Python 3.2.2 that I believe are worth noting. Instead of:

class CEnumeration(c_uint):
    __metaclass__ = EnumerationType
    _members_     = {}

You need to do:

class CEnumeration(c_uint, metaclass = EnumerationType):
    _members_     = {}

Also, int and long have been unified in Python 3 so:

def __eq__(self, other):
        if isinstance(other, (int, long)):
            return self.value == other

        return type(self) == type(other) and self.value == other.value

Becomes:

def __eq__(self, other):
        if isinstance(other, int):
            return self.value == other

        return type(self) == type(other) and self.value == other.value

Solution 2:[2]

Here is an extension of the solution from Antti Happala, using the modifications for Python 3 as suggested by Tigger, plus an exentension for arbitrary ctypes as base class (e.g. uint8 vs. uint16):

from ctypes import *


def TypedEnumerationType(tp):
    class EnumerationType(type(tp)):  # type: ignore
        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_
            else:
                _members_ = dict["_members_"]

            dict["_reverse_map_"] = {v: k for k, v in _members_.items()}
            cls = type(tp).__new__(metacls, name, bases, dict)
            for key, value in cls._members_.items():
                globals()[key] = value
            return cls

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

    return EnumerationType


def TypedCEnumeration(tp):
    class CEnumeration(tp, metaclass=TypedEnumerationType(tp)):
        _members_ = {}

        def __repr__(self):
            value = self.value
            return f"<{self.__class__.__name__}.{self._reverse_map_.get(value, '(unknown)')}: {value}>"

        def __eq__(self, other):
            if isinstance(other, int):
                return self.value == other

            return type(self) == type(other) and self.value == other.value

    return CEnumeration

Here is a small unit test for this, showing that it actually works to differentiate between unit8 and uint16 enums:

class Foo(TypedCEnumeration(c_uint16)):
        A = 42
        B = 1337

    class Bar(TypedCEnumeration(c_uint8)):
        A = 5
        B = 23

    assert isinstance(Foo(Foo.A), c_uint16)
    assert isinstance(Bar(Bar.A), c_uint8)

    assert type(Foo.A) == int
    assert Foo.A == 42
    assert str(Foo(Foo.A)) == "<Foo.A: 42>"
    assert str(Bar(Bar.B)) == "<Bar.B: 23>"

    class FooBar(Structure):
        _pack_ = 1
        _fields_ = [("foo", Foo), ("bar", Bar)]

    foobar = FooBar(Foo.A, Bar.B)

    assert sizeof(foobar) == 3
    assert foobar.foo.value == 42
    assert foobar.bar.value == 23

    assert [int(x) for x in bytes(foobar)] == [42, 0, 23]

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Tigger
Solution 2 Patrick Roocks