如何在Python中创建Mixin工厂?

4 投票
2 回答
2511 浏览
提问于 2025-04-17 12:04

我有一些类,它们被其他类包裹起来,以增加新的功能。

不过,这些包裹类没有实现直接调用被包裹类的方法,所以我们不能把包裹类和原始类互换使用。

我想动态创建一些类,这些类同时具备包裹类和原始类的功能。

我的想法是创建一个混合类(mix-in),然后用一个工厂函数把它应用到现有的类上,动态生成一个新的双功能类。这样,我就可以只写一次混合类,然后通过一个对象来使用原始功能或混合类提供的增强功能。

我想要的就是这样的效果:

class A:
    def __init__(self):
        self.name = 'A'

    def doA(self):
        print "A:", self.name


class B(A):
    def __init__(self):
        self.name = 'B'

    def doB(self):
        print "B:", self.name


class C(A):
    def __init__(self):
        self.name = 'C'

    def doC(self):
        print "C:", self.name


class D:
    def doD(self):
        print "D:", self.name


class BD(B,D):
    pass


def MixinFactory(name, base_class, mixin):
    print "Creating %s" % name
    return class(base_class, mixin)     # SyntaxError: invalid syntax

a, b, c, d, bd = A(), B(), C(), D(), BD()

bd2 = MixinFactory('BD2', B, D)()
cd = MixinFactory('CD', C, D)()

a.doA()     # A: A

b.doA()     # A: B
b.doB()     # B: B

c.doA()     # A: C
c.doC()     # C: C

bd.doA()    # A: B
bd.doB()    # B: B
bd.doD()    # D: B

bd2.doA()   # A: B
bd2.doB()   # B: B
bd2.doD()   # D: B

cd.doA()    # A: C
cd.doC()    # C: C
cd.doD()    # D: C

问题是,显然你不能直接从一个函数中返回一个类。虽然忽略语法错误,上面的代码确实展示了我想要实现的目标。

我尝试过用type()的三参数版本,但没有成功,所以我不确定这是不是正确的方法。

我认为在Python中创建这种混合工厂可能的,那么我需要了解什么才能实现它呢?


正如Niklas R评论的那样,这个回答解决了我提出的问题,关于Python动态继承:如何在实例创建时选择基类?,但Ben的回答更好地解释了原因。

2 个回答

5

你可以在任何地方声明类,而且类的声明可以用到变量来创建子类。至于类名,它就是类对象上的一个属性,叫做 .__name__。所以:

def MixinFactory(name, base_class, mixin):
  print "Creating %s" % name
  class kls(base_class, mixin):
    pass
  kls.__name__ = name
  return kls

这样就可以了。

如果你想用一行代码来实现,带有三个参数的 type 函数也可以用:

def MixinFactory(name, base_class, mixin):
  return type(name, (base_class, mixin), {})
7

其实,你可以从一个函数返回一个类。你遇到的语法错误是因为你把class这个关键词当成了可以调用的函数。

记住,类的作用就是创建一个新类,并把你选择的名字绑定到这个类上(在当前的作用域中)。所以只需要在你的函数里面放一个类的定义,然后返回这个类就可以了!

def mixinFactory(name, base, mixin):
    class _tmp(base, mixin):
        pass
    _tmp.__name__ = name
    return _tmp

正如ejucovy所提到的,你也可以直接调用type

def mixinFactory(name, base, mixin):
    return type(name, (base, mixin), {})

这样做是可行的,因为这通常就是类定义块的实际作用;它会把你在类定义块中定义的所有名字收集到一个字典里,然后把类的名字、基类的元组和这个字典传给type来构建新类。

不过,type其实就是默认的元类。类和其他对象一样,都是对象,都是某个类的实例。大多数类都是type的实例,但如果有其他的元类参与,你应该调用那个元类,而不是type,就像你不会用object来创建你类的新实例一样。

你的混入类(假设)没有定义元类,所以它应该和任何是type子类的元类兼容。因此,你可以直接使用base的类:

def mixinFactory(name, base, mixin):
    return base.__class__(name, (base, mixin), {})

不过,S.Lott的评论似乎是这个问题最好的“答案”,除非你的混入工厂做的事情比单纯创建一个新类更复杂。这种方式比任何动态类创建的变体都要清晰,而且输入的内容也更少:

class NewClass(base, mixin):
    pass

撰写回答