python - 在大量使用元类的库中,如何避免因元类冲突而惹恼用户?
问题描述
我正在编写的库大量使用元类。例如,这是一个基本的单例实现:
class SingletonMeta(type):
_instance = None
def __call__(self, *args, **kwargs):
if self._instance is None:
self._instance = super().__call__(*args, **kwargs)
return self._instance
class ExampleSingleton(metaclass=SingletonMeta):
pass
这工作得很好,但是当使用多重继承并且另一个类也有一个元类时会出现问题。元类在标准库中相当普遍。最值得注意的是abc.ABCMeta
。做一个抽象单例的天真尝试失败了:
class AbstractSingleton(ExampleSingleton, abc.ABC):
pass
Traceback (most recent call last):
File "untitled.py", line 25, in <module>
class AbstractSingleton(ExampleSingleton, abc.ABC):
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
解决方法很简单——创建一个继承自SingletonMeta
and的新元类ABCMeta
——但对于任何想要使用我的库的人来说,这真的很烦人。
class AbstractSingletonMeta(SingletonMeta, abc.ABCMeta):
pass
class AbstractSingleton(metaclass=AbstractSingletonMeta):
pass
# no metaclass conflict
处理这个问题的最佳方法是什么?
我的一些想法:
- 由于抽象类相当普遍,我可以创建
SingletonMeta
一个ABCMeta
. AbstractSingletonMeta
为了用户的方便,我可以在我的库中实现。- 由于任何可调用对象都可以用作元类,因此我可以实现一个自动合并所有父类的元类的函数。(用法如下
class AbstractSingleton(ExampleSingleton, abc.ABC, metaclass=auto_merge_metaclasses):
) - 本着“显式胜于隐式”的精神,我无能为力,让用户理清元类冲突。
解决方案
由于有关所需元类和需要组合的所有信息已经存在于基类中,并且这些信息被传递给元类调用,因此可以有一个可调用的对象来检查所有基类及其使用的元类,并且动态创建一个组合元类。
该types
模块有一些可调用的,否则可以很容易地为一组基类选择正确的元类。因此,如果您使用的所有元类都可以按任意顺序组合,那么下面的函数应该足以满足您的需求:
from types import prepare_class
def combine_meta(name, bases, namespace, **kwargs):
metaclasses = {prepare_class(name, (base,))[0] for base in bases}
metaclasses.discard(type)
if len(metaclasses) > 1:
meta_name = '_'.join(mcs.__name__ for mcs in metaclasses)
metaclass = combine_meta(meta_name, tuple(metaclasses), {})
elif len(metaclasses) == 1:
metaclass = metaclasses.pop()
else:
metaclass = type
return metaclass(name, bases, namespace, **kwargs)
我已经在交互式解释器中用这个序列测试了这个,它工作得这么远:
class M1(type): pass
class M2(type): pass
class A(metaclass=M1): pass
class B(metaclass=M2): pass
class C(A, B): pass # This raises a metaclassconflict
class C(A, B, metaclass=combine_meta): pass
推荐阅读
- javascript - 如何添加 for 循环返回的数字?
- javascript - 在 Node.js 中使用 Cognito 凭据登录并调用 API 网关
- python - 为什么我的 nltk.pos_tag 标记字母而不是单词
- javascript - 自动完成半径搜索在我的谷歌地图中不起作用
- collections - Kotlin Collections 标准库中的这些函数是重复的,还是有细微的差别?
- java - 如何从java中的while循环返回String的所有值?
- regex - 正则表达式提取数据
- amazon-web-services - 在 CloudFormation 模板中自动设置 ListenerRule 优先级
- scala - 火花数据集的正确单子 flatMap 操作?
- java - RXJava - 当其中一个数据可用时永久切换可观察对象