Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use a metaclass only for subclasses

Is there a way in Python 3.2 or later to define a class whose subclasses should be created using a specific metaclass without the class itself being created using that metaclass?

An example to demonstrate what I mean: Let's say I want to create an Enum class whose subclasses can be used to define enum types. An enum type would have a fixed number of instances, each of which has a distinct int (or other) value. An enum type would be declared by creating a subclass of Enum and assigning the values to attributes of that class. Through the metaclass of Enum, the values of those attributes would be replace by instances of the new class.

The Enum class may also define some class or instance methods or members that can be used on its subclasses or their instances.

class EnumMeta(type):
    def __new__(mcs, what, bases, dict):
        cls = super().__new__(mcs, what, bases, { })

        cls._instances_by_value = { }

        for k, v in dict.items():
            if k.startswith('__') or k == 'get_by_value':
                setattr(cls, k, v)
            else:
                instance = cls(k, v)

                setattr(cls, k, instance)
                cls._instances_by_value[v] = instance

        return cls


class Enum(metaclass = EnumMeta):
    def __init__(self, name, value):
        self.name = name
        self.value = value

    def __repr__(self):
        return '{}.{}'.format(type(self).__name__, self.name)

    @classmethod
    def get_by_value(cls, value):
        return cls._instances_by_value[value]

An example for creating such an enum type:

class Boolean(Enum):
    true = 0
    false = 1
    file_not_found = 2

print(Boolean.true) # Prints Boolean.true
print(Boolean.get_by_value(1)) # Prints Boolean.false

In the definition of EnumMeta.__new__, you can see that I had to exclude get_by_value from the processing of the metaclass. I often run into this problem when I want to process the members of a class definition in some way using a metaclass.

What is the preferred approach to exclude the base class (Enum in the example) from processing by the metaclass (EnumMeta in the example) while using the metaclass for all subclasses of the base class?

I don't think that excluding all members of the base class while processing the members of a subclass is the preferred approach. I don't care about the value of type(Enum). It can either be EnumMeta or type.

like image 724
Feuermurmel Avatar asked Jan 20 '14 10:01

Feuermurmel


1 Answers

Your 'parent' Enum won't have any bases, only classes derived from Enum will have a non-empty bases tuple. Use this to easily bail out early to create the base class with a regular __new__ call:

class EnumMeta(type):
    def __new__(mcs, what, bases, dict):
        if not bases:
            # Enum itself
            return super().__new__(mcs, what, bases, dict)

        # Process subclass creation
like image 102
Martijn Pieters Avatar answered Oct 11 '22 00:10

Martijn Pieters