简体   繁体   English

如何使用 Python 3 元类动态生成中间类

[英]How to dynamically generate an intermediate class with Python 3 metaclasses

See full gist here 在此处查看完整要点

Consider the case where we have a simple metaclass that generates the __init__ method for a class考虑我们有一个简单的元类为类生成__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

Now this is all good and well to use现在这一切都很好用

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

But we have problems when subclassing, because if you want to subclass the __init__ method what of course happens is the subclassed method just gets overwritten.但是我们在子类化时遇到了问题,因为如果你想子类化__init__方法,当然会发生子类化的方法被覆盖。

class SubTest(Test):

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

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

This will obviously give the这显然会给

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

The only way I can think to solve this is to create an intermediate class in the __new__ method of the metaclass and make this the base for the class we are creating.我能想到的解决这个问题的__new__方法是在元类的__new__方法中创建一个中间类,并使其成为我们正在创建的类的基础。 But I can't get this to work, I tried something like this但我不能让它工作,我试过这样的事情

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)

But this gives me recursion error但这给了我递归错误

RecursionError: maximum recursion depth exceeded while calling a Python object

The infinite recursion is caused by the fact that the type constructor can return an instance of your metaclass.无限递归是由type构造函数可以返回元类的实例这一事实引起的。 In this line here:在此行中:

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

If any of the base classes in bases is an instance of TestType , then the subclass will also be an instance of TestType .如果bases中的TestType类是TestType的实例,则子类也将是TestType的实例。 That is why Test can be created with no problems, but SubTest causes infinite recursion.这就是为什么可以SubTest地创建Test ,但SubTest会导致无限递归。

The fix is simple: Create the intermediate class without an __init__ attribute.解决方法很简单:创建没有__init__属性的中间类。 That way if '__init__' in attrs: will be False , and the endless recursion is avoided.这样, 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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM