从 Python 中的元类继承
Inheritance from metaclasses in Python
我有简单的元数据class,它将 classes 以“get_”开头的方法转换为属性:
class PropertyConvertMetaclass(type):
def __new__(mcs, future_class_name, future_class_parents, future_class_attr):
new_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
if name.startswith('get_'):
new_attr[name[4:]] = property(val)
else:
new_attr[name] = val
return type.__new__(mcs, future_class_name, future_class_parents, new_attr)
假设我有 TestClass:
class TestClass():
def __init__(self, x: int):
self._x = x
def get_x(self):
print("this is property")
return self._x
我希望它像这样工作:我创建了一些新的 class 有点继承了它们
class NewTestClass(TestClass, PropertyConvertMetaclass):
pass
我可以像这样重用他们的两种方法:
obj = NewTestClass(8)
obj.get_x() # 8
obj.x # 8
按照我的想法,我应该创建一个新的 class,让我们将其命名为 PropertyConvert 并使 NewTestClass 继承自它:
class PropertyConvert(metaclass=PropertyConvertMetaclass):
pass
class NewTestClass(TestClass, PropertyConvert):
pass
但这无济于事,我仍然无法将新的 属性 方法与 NewClassTest 一起使用。如何让 PropertyConvert 继承其兄弟的所有方法,而不在 NewClassTest 中做任何事情,仅更改 PropertyConverterMetaclass 或 PropertyConverter?我是 metaclasses 的新手,如果这个问题看起来很愚蠢,我很抱歉。
当您执行 TestClass():
时,class 的主体在名称空间中为 运行,该名称空间成为 class __dict__
。 metaclass 只是通过 __new__
和 __init__
通知命名空间的构造。在这种情况下,您已将 TestClass
的元 class 设置为 type
.
当您从 TestClass
继承时,例如。 G。对于 class NewTestClass(TestClass, PropertyConverter):
,您编写的 PropertyConvertMetaclass
版本仅在 NewTestClass
的 __dict__
上运行。 TestClass
已在此时创建,没有任何属性,因为它的元 class 方式 type
,而子 class 是空的,所以您看不到任何属性。
这里有几个可能的解决方案。更简单但由于您的任务而无法实现的是 class TestClass(metaclass=PropertyConvertMetaclass):
。 TestClass
的所有子代都将拥有 PropertyConvertMetaclass
,因此所有 getter 都将转换为属性。
另一种方法是仔细查看 PropertyConvertMetaclass.__new__
的参数。一般情况下,你只对future_class_attr
属性进行操作。但是,您也可以访问 future_class_bases
。如果您想升级 PropertyConverter
的直系兄弟姐妹,这就是您所需要的:
class PropertyConvertMetaclass(type):
def __new__(mcs, future_class_name, future_class_parents, future_class_attr):
# The loop is the same for each base __dict__ as for future_class_attr,
# so factor it out into a function
def update(d):
for name, value in d.items():
# Don't check for dunders: dunder can't start with `get_`
if name.startswith('get_') and callable(value):
prop = name[4:]
# Getter and setter can't be defined in separate classes
if 'set_' + prop in d and callable(d['set_' + prop]):
setter = d['set_' + prop]
else:
setter = None
if 'del_' + prop in d and callable(d['del_' + prop]):
deleter = d['del_' + prop]
else:
deleter = None
future_class_attr[prop] = property(getter, setter, deleter)
update(future_class_dict)
for base in future_class_parents:
# Won't work well with __slots__ or custom __getattr__
update(base.__dict__)
return super().__new__(mcs, future_class_name, future_class_parents, future_class_attr)
这对于您的作业来说可能已经足够了,但缺乏一定的技巧。具体来说,我能看到的不足有两个:
- 没有超出直接基数 classes 的查找。
- 您不能在一个 class 中定义 getter 而在另一个中定义 setter。
要解决第一个问题,您将必须遍历 class 的 MRO。由于 ,使用 __init__
而不是 class 之前的字典,在完全构造的 class 上更容易做到这一点。我将通过在创建任何属性之前创建可用 getter 和 setter 的 table 来解决第二个问题。您还可以通过这样做使属性尊重 MRO。使用 __init__
的唯一麻烦是您必须在 class 上调用 setattr
而不是简单地更新它的未来 __dict__
.
class PropertyConvertMetaclass(type):
def __init__(cls, class_name, class_parents, class_attr):
getters = set()
setters = set()
deleters = set()
for base in cls.__mro__:
for name, value in base.__dict__.items():
if name.startswith('get_') and callable(value):
getters.add(name[4:])
if name.startswith('set_') and callable(value):
setters.add(name[4:])
if name.startswith('del_') and callable(value):
deleters.add(name[4:])
for name in getters:
def getter(self, *args, **kwargs):
return getattr(super(cls, self), 'get_' + name)(*args, **kwargs)
if name in setters:
def setter(self, *args, **kwargs):
return getattr(super(cls, self), 'set_' + name)(*args, **kwargs)
else:
setter = None
if name in deleters:
def deleter(self, *args, **kwargs):
return getattr(super(cls, self), 'del_' + name)(*args, **kwargs)
else:
deleter = None
setattr(cls, name, property(getter, setter, deleter)
您在 metaclass 的 __init__
中所做的任何事情都可以使用 class 装饰器轻松完成。主要区别在于元class 将应用于所有子 classes,而装饰器仅在使用它的地方应用。
没有什么是“不可能”的。
这是一个问题,无论多么不寻常,都可以用 metaclasses.
来解决
你的方法很好 - 你遇到的问题是,当你查看“future_class_attr”(也称为 class 正文中的名称空间)时,它只包含方法和class 当前正在定义 的属性。在您的示例中,NewTestClass
为空,“future_class_attr”也是如此。
克服这个问题的方法是检查所有基础 classes,寻找与您正在寻找的模式相匹配的方法,然后创建适当的 属性.
在之前创建目标 class 正确执行此操作会很棘手 - 因为必须在所有对象的正确 mro(方法解析顺序)中进行属性搜索superclasses - 可能会有很多极端情况。 (但请注意,这并非“不可能”)
但是在 创建新的 class 之后,没有什么能阻止您这样做 。为此,您可以将 super().__new__(mcls, ...)
的 return 值分配给一个变量(顺便说一下,更喜欢使用 super().__new__
而不是硬编码 type.__new__
:这允许您的元 class 进行协作并与 collections.ABC 或 enum.Enum 结合使用)。该变量是您完成的 class 并且您可以在其上使用 dir
来检查所有属性和方法名称,已经合并所有 superclasses - 然后,只需创建您的新属性和然后用 setattr(cls_variable, property_name, property_object)
.
分配给新创建的 class
更好的是,编写 metaclass __init__
而不是它的 __new__
方法:你检索已经创建的新 class,并且可以继续自省它dir
并立即添加属性。 (尽管 你的 class 不需要它,但不要忘记调用 super().__init__(...)
。)
此外,请注意,自 Python 3.6 起,如果仅在一个基地 class.
我的问题的解决方案之一是在 PropertyConvertMetaclass 中解析 parents' 指令:
class PropertyConvertMetaclass(type):
def __new__(mcs, future_class_name, future_class_parents, future_class_attr):
new_attr = {}
for parent in future_class_parents:
for name, val in parent.__dict__.items():
if not name.startswith('__'):
if name.startswith('get_'):
new_attr[name[4:]] = property(val, parent.__dict__['set_' + name[4:]])
new_attr[name] = val
for name, val in future_class_attr.items():
if not name.startswith('__'):
if name.startswith('get_'):
new_attr[name[4:]] = property(val, future_class_attr['set_'+name[4:]])
new_attr[name] = val
return type.__new__(mcs, future_class_name, future_class_parents, new_attr)
我有简单的元数据class,它将 classes 以“get_”开头的方法转换为属性:
class PropertyConvertMetaclass(type):
def __new__(mcs, future_class_name, future_class_parents, future_class_attr):
new_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
if name.startswith('get_'):
new_attr[name[4:]] = property(val)
else:
new_attr[name] = val
return type.__new__(mcs, future_class_name, future_class_parents, new_attr)
假设我有 TestClass:
class TestClass():
def __init__(self, x: int):
self._x = x
def get_x(self):
print("this is property")
return self._x
我希望它像这样工作:我创建了一些新的 class 有点继承了它们
class NewTestClass(TestClass, PropertyConvertMetaclass):
pass
我可以像这样重用他们的两种方法:
obj = NewTestClass(8)
obj.get_x() # 8
obj.x # 8
按照我的想法,我应该创建一个新的 class,让我们将其命名为 PropertyConvert 并使 NewTestClass 继承自它:
class PropertyConvert(metaclass=PropertyConvertMetaclass):
pass
class NewTestClass(TestClass, PropertyConvert):
pass
但这无济于事,我仍然无法将新的 属性 方法与 NewClassTest 一起使用。如何让 PropertyConvert 继承其兄弟的所有方法,而不在 NewClassTest 中做任何事情,仅更改 PropertyConverterMetaclass 或 PropertyConverter?我是 metaclasses 的新手,如果这个问题看起来很愚蠢,我很抱歉。
当您执行 TestClass():
时,class 的主体在名称空间中为 运行,该名称空间成为 class __dict__
。 metaclass 只是通过 __new__
和 __init__
通知命名空间的构造。在这种情况下,您已将 TestClass
的元 class 设置为 type
.
当您从 TestClass
继承时,例如。 G。对于 class NewTestClass(TestClass, PropertyConverter):
,您编写的 PropertyConvertMetaclass
版本仅在 NewTestClass
的 __dict__
上运行。 TestClass
已在此时创建,没有任何属性,因为它的元 class 方式 type
,而子 class 是空的,所以您看不到任何属性。
这里有几个可能的解决方案。更简单但由于您的任务而无法实现的是 class TestClass(metaclass=PropertyConvertMetaclass):
。 TestClass
的所有子代都将拥有 PropertyConvertMetaclass
,因此所有 getter 都将转换为属性。
另一种方法是仔细查看 PropertyConvertMetaclass.__new__
的参数。一般情况下,你只对future_class_attr
属性进行操作。但是,您也可以访问 future_class_bases
。如果您想升级 PropertyConverter
的直系兄弟姐妹,这就是您所需要的:
class PropertyConvertMetaclass(type):
def __new__(mcs, future_class_name, future_class_parents, future_class_attr):
# The loop is the same for each base __dict__ as for future_class_attr,
# so factor it out into a function
def update(d):
for name, value in d.items():
# Don't check for dunders: dunder can't start with `get_`
if name.startswith('get_') and callable(value):
prop = name[4:]
# Getter and setter can't be defined in separate classes
if 'set_' + prop in d and callable(d['set_' + prop]):
setter = d['set_' + prop]
else:
setter = None
if 'del_' + prop in d and callable(d['del_' + prop]):
deleter = d['del_' + prop]
else:
deleter = None
future_class_attr[prop] = property(getter, setter, deleter)
update(future_class_dict)
for base in future_class_parents:
# Won't work well with __slots__ or custom __getattr__
update(base.__dict__)
return super().__new__(mcs, future_class_name, future_class_parents, future_class_attr)
这对于您的作业来说可能已经足够了,但缺乏一定的技巧。具体来说,我能看到的不足有两个:
- 没有超出直接基数 classes 的查找。
- 您不能在一个 class 中定义 getter 而在另一个中定义 setter。
要解决第一个问题,您将必须遍历 class 的 MRO。由于 __init__
而不是 class 之前的字典,在完全构造的 class 上更容易做到这一点。我将通过在创建任何属性之前创建可用 getter 和 setter 的 table 来解决第二个问题。您还可以通过这样做使属性尊重 MRO。使用 __init__
的唯一麻烦是您必须在 class 上调用 setattr
而不是简单地更新它的未来 __dict__
.
class PropertyConvertMetaclass(type):
def __init__(cls, class_name, class_parents, class_attr):
getters = set()
setters = set()
deleters = set()
for base in cls.__mro__:
for name, value in base.__dict__.items():
if name.startswith('get_') and callable(value):
getters.add(name[4:])
if name.startswith('set_') and callable(value):
setters.add(name[4:])
if name.startswith('del_') and callable(value):
deleters.add(name[4:])
for name in getters:
def getter(self, *args, **kwargs):
return getattr(super(cls, self), 'get_' + name)(*args, **kwargs)
if name in setters:
def setter(self, *args, **kwargs):
return getattr(super(cls, self), 'set_' + name)(*args, **kwargs)
else:
setter = None
if name in deleters:
def deleter(self, *args, **kwargs):
return getattr(super(cls, self), 'del_' + name)(*args, **kwargs)
else:
deleter = None
setattr(cls, name, property(getter, setter, deleter)
您在 metaclass 的 __init__
中所做的任何事情都可以使用 class 装饰器轻松完成。主要区别在于元class 将应用于所有子 classes,而装饰器仅在使用它的地方应用。
没有什么是“不可能”的。 这是一个问题,无论多么不寻常,都可以用 metaclasses.
来解决你的方法很好 - 你遇到的问题是,当你查看“future_class_attr”(也称为 class 正文中的名称空间)时,它只包含方法和class 当前正在定义 的属性。在您的示例中,NewTestClass
为空,“future_class_attr”也是如此。
克服这个问题的方法是检查所有基础 classes,寻找与您正在寻找的模式相匹配的方法,然后创建适当的 属性.
在之前创建目标 class 正确执行此操作会很棘手 - 因为必须在所有对象的正确 mro(方法解析顺序)中进行属性搜索superclasses - 可能会有很多极端情况。 (但请注意,这并非“不可能”)
但是在 创建新的 class 之后,没有什么能阻止您这样做 。为此,您可以将 super().__new__(mcls, ...)
的 return 值分配给一个变量(顺便说一下,更喜欢使用 super().__new__
而不是硬编码 type.__new__
:这允许您的元 class 进行协作并与 collections.ABC 或 enum.Enum 结合使用)。该变量是您完成的 class 并且您可以在其上使用 dir
来检查所有属性和方法名称,已经合并所有 superclasses - 然后,只需创建您的新属性和然后用 setattr(cls_variable, property_name, property_object)
.
更好的是,编写 metaclass __init__
而不是它的 __new__
方法:你检索已经创建的新 class,并且可以继续自省它dir
并立即添加属性。 (尽管 你的 class 不需要它,但不要忘记调用 super().__init__(...)
。)
此外,请注意,自 Python 3.6 起,如果仅在一个基地 class.
我的问题的解决方案之一是在 PropertyConvertMetaclass 中解析 parents' 指令:
class PropertyConvertMetaclass(type):
def __new__(mcs, future_class_name, future_class_parents, future_class_attr):
new_attr = {}
for parent in future_class_parents:
for name, val in parent.__dict__.items():
if not name.startswith('__'):
if name.startswith('get_'):
new_attr[name[4:]] = property(val, parent.__dict__['set_' + name[4:]])
new_attr[name] = val
for name, val in future_class_attr.items():
if not name.startswith('__'):
if name.startswith('get_'):
new_attr[name[4:]] = property(val, future_class_attr['set_'+name[4:]])
new_attr[name] = val
return type.__new__(mcs, future_class_name, future_class_parents, new_attr)