特定于案例的混合?

问题描述

python 有没有办法根据其他参数继承不同的 mixin 类?示例:

a = MyClass(case='A')  # spawns a class instance which inherits MixinA
b = MyClass(case='B')  # spawns a class instance which inherits MixinB

MixinAMixinB 都需要访问 MyClass 的数据,但本身不托管任何数据。它们都定义了不同版本的 some_methodself.some_method() 会在 MyClass 中引用它,但是根据传递给 {{1} 的 case 参数做不同的事情}}。

我不确定这是否有意义,或者是否有更好的设计。在我的应用程序中,性能是关键,所以我想在调用MyClassMixinA 定义的 case-specific 方法时尽可能地避免开销。我在某处读到使用 mixins 会对此有好处。但也许有更好的方法

解决方法

正如我在评论中所说,这种事情可以使用元类来完成。以下是如何做到这一点:

class MixinA:
    def some_method(self):
        print('In MixinA.some_method()')


class MixinB:
    def some_method(self):
        print('In MixinB.some_method()')


class MyMetaClass(type):
    mixins = {'A': MixinA,'B': MixinB}  # Supported mix-ins.

    def __new__(cls,name,bases,classdict,**kwargs):
        case = kwargs.pop('case')
        mixin = cls.mixins.get(case)
        if not mixin:
            raise TypeError(f'Unknown mix-in case selector {case!r} specified')
        else:
            bases += (mixin,)

        return type.__new__(cls,**kwargs)


class ClassA(metaclass=MyMetaClass,case='A'): ...
class ClassB(metaclass=MyMetaClass,case='B'): ...

a = ClassA()
b = ClassB()

a.some_method()  # -> In MixinA.some_method()
b.some_method()  # -> In MixinB.some_method()


至少有几种方法可以在没有元类的情况下完成。一种方法是利用添加到 Python 3.6 中的 __init_subclass__() 特殊方法。

class MixinA:
    def some_method(self):
        print('In MixinA.some_method()')


class MixinB:
    def some_method(self):
        print('In MixinB.some_method()')


class MyClass:

    mixins = {'A': MixinA,'B': MixinB}  # Supported mix-ins.

    def __init_subclass__(cls,/,case,**kwargs):
        super().__init_subclass__(**kwargs)
        mixin = cls.mixins.get(case)
        if not mixin:
            raise TypeError(f'Unknown mix-in case selector {case!r} specified')
        else:
            cls.__bases__ += (mixin,)


class ClassA(MyClass,case='A'): ...
class ClassB(MyClass,case='B'): ...


a = ClassA()
b = ClassB()

a.some_method()  # -> In MixinA.some_method()
b.some_method()  # -> In MixinB.some_method()


另一种方法是通过普通函数,这是一种可能性,因为在 Python 中类本身就是“first class objects”。

以下是通过我命名为 mixer() 的函数执行此操作的示例:

class MixinA:
    def some_method(self):
        print('In MixinA.some_method()')


class MixinB:
    def some_method(self):
        print('In MixinB.some_method()')


class MyClass: ...


MIXINS = {'A': MixinA,'B': MixinB}  # Supported mix-in classes.

def mixer(cls,*,case):
    mixin = MIXINS.get(case)
    if not mixin:
        raise TypeError(f'Unknown mix-in case selector {case!r} specified')

    mixed_classname = f'{cls.__name__}Plus{mixin.__name__}'
    return type(mixed_classname,(cls,mixin),{})


ClassA = mixer(MyClass,case='A')
ClassB = mixer(MyClass,case='B')

a = ClassA()
b = ClassB()

a.some_method()  # -> In MixinA.some_method()
b.some_method()  # -> In MixinB.some_method()

如您所见,所有这些技术都存在一个在 class factory pattern 中经常看到的限制(这或多或少是我们在这里处理的),即基类或元类方法或指定的函数必须知道子类或混合类的所有可能性才能使用。

虽然有一些解决方法可以缓解这种情况——至少在使用其他方法时,请参阅我对 Improper use of __new__ to generate classes? 的回答,它可以与“类工厂”模式一起使用。

但是,在这种特定情况下,您可以通过简单地传递混合 class 作为参数而不是 case 选择器来避免混合类的硬编码.