从属性python 3动态更改类父级

时间:2017-06-04 01:54:12

标签: python-3.x class metaclass

我希望新类根据创建实例时给出的属性动态地从不同的父级继承。到目前为止,我尝试过这样的事情:

class Meta(type):
    chooser = None
    def __call__(cls, *args, **kwars):
        if kwargs['thingy'] == 'option':
            Meta.choose = option
        return super().__call__(*args, **kwargs)

    def __new__(cls, name, parents, attrs):
        if Meta.choose == option:
            bases = (parent1)
        return super().__new__(cls, name, parents, attrs)

它不起作用,是否有一种方法,根据实例的一个参数,我可以动态地为该类选择一个父类?

1 个答案:

答案 0 :(得分:0)

首先,让我们在代码中修复一个微不足道的错误,然后深入研究"真正的问题":bases参数需要是一个元组。当你执行bases=(option)右侧不是元组时 - 它只是一个带括号的表达式,它将被解析并作为非元组option传递。

每当您需要为基础创建元组时,将其更改为bases=(option,)

第二个错误更具概念性,可能就是为什么你没有让它在varios尝试中发挥作用:元类的__call__方法不是人们常用的方法。为了保持较长的历史记录,__call__的{​​{1}}方法被称为协调调用该类实例的__class____new__方法 - 由Python自动生成,来自__init__的{​​{1}}具有此机制。当您将其转置到您的元类时,如果您的元类本身的__call__type方法是,则可能会释放您的元类的__call__方法即将被调用(定义类时)。换句话说 - 使用的__new__位于" meta-meta" class(再次,类型)。

您编写的__init__方法将在创建自定义类的实例时使用(这就是您的意图),并且不会影响类创建,因为它赢得了t调用元类' __call__ - 只是班级__call__本身。 (这不是你想要的)。

所以,您需要的是,__new__内部不要使用您收到的相同参数调用__new__:这会将__call__传递给super().__call__'调用,cls的基础在运行元类type时被烘焙 - 它只是在声明类体本身时才会发生。

你必须在这个cls中动态创建一个新类,或者使用一个预先填充的表,然后将这个动态创建的类传递给__new__

但是 - 在一天结束时,人们可以感觉到所有这一切都可以通过 factory 函数完成,因此不需要为此创建这个超级复杂的元类机制 - 以及其他Python工具,如linters和静态分析器(在您或您的同事可能使用的IDE中嵌入)可能会更好地工作。

使用工厂功能的解决方案:

__call__

如果你不想创建一个新的类,但是想要与commo base共享一些预先存在的类,只需创建一个缓存字典,并使用dcit' s type.__call__方法:

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方法将在dict上存储第二个参数。)

使用元类:

<强>更新

早餐后:-)我想出了这个: 使您的元类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) 函数,该函数将创建一个新类,动态地使用所需的基类,或者为相同的选项使用高速缓存的类。但与其他示例不同,使用元类将原始参数分配给类创建以创建派生类:

__new__

为了简化示例,这只会覆盖使用此元类的类上的任何explict __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 并且在该过程中丢弃了元类。通过编写更仔细的代码可以解决这两个问题,但在这里它将变得复杂。