首先,让我们修复代码中的一个小错误,然后深入研究“真正的问题”:bases 参数需要是一个元组。当您执行 bases=(option) 时,右侧不是元组 - 它只是一个带括号的表达式,将被解析并作为非元组 option 传递。
当您需要为基础创建元组时,将其更改为 bases=(option,)。
第二个错误更具概念性,这可能是您没有让它在各种尝试中起作用的原因:元类的__call__ 方法不是人们通常会摆弄的东西。为了简短起见,__class__ 的 __call__ 方法被调用来协调该类实例的 __new__ 和 __init__ 方法的调用 - 这是由 Python 自动生成的,它是具有此机制的type 中的__call__。当您将其转置到您的元类时,您可能会意识到当您的元类本身的 __new__ 和 __init__ 方法即将被调用时(当定义了一个类)。换句话说 - 使用的 __call__ 位于“元元”类(同样是类型)上。
您编写的 __call__ 方法将在您的自定义类的实例被创建时使用(这就是您的意图),并且不会影响类的创建,因为它不会调用元类' __new__ - 只是类 __new__ 本身。 (这不是你想要的)。
所以,你需要的是,从__call__ 内部不要使用你收到的相同参数调用super().__call__:这会将cls 传递给type 的调用,cls 的基础是当元类 __new__ 运行时就被烘焙了——它只会在类主体本身被声明时发生。
您必须在此__call__ 中动态创建一个新类,或使用预先填写的表之一,然后他们将该动态创建的类传递给type.__call__。
但是 - 归根结底,我们可以意识到所有这些都可以通过 factory 函数来完成,因此没有必要为此创建这种超级复杂的元类机制- 其他 Python 工具,例如 linter 和静态分析器(嵌入在您或您的同事可能正在使用的 IDE 中)可能会更好地使用它。
使用工厂函数的解决方案:
def factory(cls, *args, options=None, **kwargs):
if options == 'thingy':
cls = type(cls.__name__, (option1, ), cls.__dict__)
elif options = 'other':
...
return cls(*args, **kwargs)
如果你不想在每次调用时都创建一个新类,但想共享几个具有公共基础的预先存在的类,只需创建一个缓存字典,并使用字典的 setdefault 方法:
class_cache = {}
def factory(cls, *args, options=None, **kwargs):
if options == 'thingy':
cls = class_cache.setdefault((cls.__name__, options),
type(cls.__name__, (option1, ), cls.__dict__))
elif options = 'other':
...
return cls(*args, **kwargs)
(如果键(名称,选项)尚不存在,setdefault 方法会将第二个参数存储在 dict 中)。
使用元类:
更新
早餐后 :-) 我想出了这个:
让你的元类__new__ 在创建的类本身上注入一个__new__ 函数,该函数将创建一个新类,动态地具有所需的基础,或者为相同的选项使用缓存的类。但与其他示例不同的是,使用元类将原始参数注释到类创建以创建派生类:
parameter_buffer = {}
derived_classes = {}
class Meta:
def __new__(metacls, name, bases, namespace):
cls = super().__new__(metacls, name, bases, namespace)
parameter_buffer[cls] = (name, bases, namespace)
def __new__(cls, *args, option=None, **kwargs):
if option is None:
return original_new(cls, *args, **kwargs)
name, bases, namespace = parameter_buffer[cls]
if option == 'thingy':
bases = (option1,)
elif option== 'thingy2':
...
if not (cls, bases) in derived_classes:
derived_classes[cls, bases] = type(name, bases, namespace)
return derived_classes[cls, bases](*args, **kwargs)
cls.__new__ = __new__
return cls
为了使示例简短,这只是覆盖使用此元类的类上的任何显式__new__ 方法。此外,以这种方式创建的派生类本身并不具有相同的能力,因为它们是调用type 创建的,并且元类在此过程中被丢弃。这两件事都可以通过编写更仔细的代码来解决,但在这里作为示例会变得复杂。