如何使用 Python 3 metaclasses 动态生成中间 class
How to dynamically generate an intermediate class with Python 3 metaclasses
查看完整要点here
考虑我们有一个简单的 metaclass 的情况,它为 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
现在这一切都很好用了
test = Test('hello')
assert test.get_message() == 'hello'
但是我们在 subclassing 时遇到问题,因为如果你想 subclass __init__
方法当然会发生 subclassed 方法只是被覆盖。
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
我认为解决这个问题的唯一方法是在 metaclass 的 __new__
方法中创建一个中间体 class 并将其作为 class 的基础=] 我们正在创造。但是我无法让它工作,我试过这样的东西
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
无限递归是由于 type
构造函数可以 return 你的元 class 的一个实例。
在此处的这一行中:
interm_base = type(cname + 'Intermediate', bases, attrs)
如果 bases
中的任何基础 class 是 TestType
的实例,那么子 class 也将是 [=14= 的实例].这就是为什么 Test
可以毫无问题地创建,但是 SubTest
导致无限递归。
修复很简单:创建没有 __init__
属性的中间 class 。这样 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)
查看完整要点here
考虑我们有一个简单的 metaclass 的情况,它为 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
现在这一切都很好用了
test = Test('hello')
assert test.get_message() == 'hello'
但是我们在 subclassing 时遇到问题,因为如果你想 subclass __init__
方法当然会发生 subclassed 方法只是被覆盖。
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
我认为解决这个问题的唯一方法是在 metaclass 的 __new__
方法中创建一个中间体 class 并将其作为 class 的基础=] 我们正在创造。但是我无法让它工作,我试过这样的东西
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
无限递归是由于 type
构造函数可以 return 你的元 class 的一个实例。
在此处的这一行中:
interm_base = type(cname + 'Intermediate', bases, attrs)
如果 bases
中的任何基础 class 是 TestType
的实例,那么子 class 也将是 [=14= 的实例].这就是为什么 Test
可以毫无问题地创建,但是 SubTest
导致无限递归。
修复很简单:创建没有 __init__
属性的中间 class 。这样 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)