Как динамически генерировать промежуточный класс с метаклассами Python 3 - PullRequest
0 голосов
/ 09 октября 2018

См. Полный текст здесь

Рассмотрим случай, когда у нас есть простой метакласс, который генерирует метод __init__ для класса

class TestType(type):

    def __new__(cls, cname, bases, attrs):
        # Dynamically create the __init__ function
        def init(self, message):
            self.message = message

        # Assign the created function as the __init__ method.
        attrs['__init__'] = init

        # Create the class.
        return super().__new__(cls, cname, bases, attrs)


class Test(metaclass=TestType):

    def get_message(self):
        return self.message

Теперь этовсе хорошо и хорошо для использования

test = Test('hello')
assert test.get_message() == 'hello'

Но у нас есть проблемы при создании подклассов, потому что если вы хотите создать подкласс метода __init__, то, конечно, происходит, что метод подклассов просто перезаписывается.

class SubTest(Test):

    def __init__(self, first, second):
        self.first = first
        self.second = second
        super().__init__(first + ' ' second)

subtest = SubTest('hello', 'there')

Это, очевидно, даст

TypeError: init() takes 2 positional arguments but 3 were given

. Единственный способ решить эту проблему - создать промежуточный класс в методе метакласса __new__ и сделать его основой для класса.мы создаем.Но я не могу заставить это работать, я пробовал что-то вроде этого

class TestType(type):

    def __new__(cls, cname, bases, attrs):
        # Dynamically create the __init__ function
        def init(self, message):
            self.message = message

        # If the __init__ method is being subclassed
        if '__init__' in attrs:
            # Store the subclass __init__
            sub_init = attrs.pop('__init__')

            # Assign the created function as the __init__ method.
            attrs['__init__'] = init

            # Create an intermediate class to become the base.
            interm_base = type(cname + 'Intermediate', bases, attrs)

            # Add the intermediate class as our base.
            bases = (interm_base,)

            # Assign the subclass __init__ as the __init__ method. 
            attrs['__init__'] = sub_init

        else:
            # Assign the created function as the __init__ method.
            attrs['__init__'] = init

        # Create the class.
        return super().__new__(cls, cname, bases, attrs)

Но это дает мне ошибку рекурсии

RecursionError: maximum recursion depth exceeded while calling a Python object

1 Ответ

0 голосов
/ 09 октября 2018

Бесконечная рекурсия вызвана тем фактом, что конструктор type может возвращать экземпляр вашего метакласса.В этой строке:

interm_base = type(cname + 'Intermediate', bases, attrs)

Если какой-либо из базовых классов в bases является экземпляром TestType, то подкласс также будет экземпляром TestType.Вот почему Test может быть создан без проблем, но SubTest вызывает бесконечную рекурсию.

Исправление простое: создайте промежуточный класс без атрибута __init__.Таким образом, if '__init__' in attrs: будет False, и бесконечная рекурсия будет исключена.

class TestType(type):
    def __new__(cls, cname, bases, attrs):
        # Dynamically create the __init__ function
        def init(self, message):
            self.message = message

        # If the __init__ method is being subclassed
        if '__init__' in attrs:
            # Create an intermediate class to become the base.
            interm_base = type(cname + 'Intermediate', bases, {})

            # Add the intermediate class as our base.
            bases = (interm_base,)
        else:
            # Assign the created function as the __init__ method.
            attrs['__init__'] = init

        # Create the class.
        return super().__new__(cls, cname, bases, attrs)
...