用元类参数化继承

6 投票
2 回答
1427 浏览
提问于 2025-04-16 05:06

我看了一些关于Python元类的教程。虽然我之前从来没有用过元类,但我现在需要一个来处理一些相对简单的事情,而所有的教程似乎都在讲一些更复杂的用法。我基本上想创建一个模板类,这个类有一些预先指定的内容,但它的基类是作为参数传入的。因为我这个想法是从C++/D的模板中得到的,所以我想给大家看看我想在C++中写的代码示例:

template<class T>
    class Foo : T {
        void fun() {}
    }

2 个回答

0

在Python中,这个概念没有意义,因为Python没有模板。我的理解是,C++中的参数化模板(我对这个理解得不太清楚,因为我已经很多年没接触过了)就像一个类工厂,可以根据你给它的类创建一个子类,并且可以添加额外的方法或属性。

在Python中,你可以使用一个工厂函数来实现类似的功能,这个函数接收一个类,并在运行时返回一个新的类:

In [1]: def subclassFactory(cls):
   ...:     class Foo(cls):
   ...:         def fun(self):
   ...:             return "this is fun"
   ...:     return Foo
   ...: 

In [2]: class A(object):
   ...:     pass
   ...: 

In [5]: C = subclassFactory(A)

In [6]: C
Out[6]: <class '__main__.Foo'>
In [7]: c = C()
In [9]: c.fun()
Out[9]: 'this is fun'
In [10]: isinstance(c, A)
Out[10]: True
10

虽然确实可以通过元类来实现你想要的功能,但其实在Python中,类本身就是对象,所以你完全可以不使用元类。这个意思是——令人惊讶的是,几乎只需要把C++代码直接翻译过来就可以了。因为这样做相对简单,而且在Python 2和3中都能正常工作,不需要任何修改。

def template(class_T):
    """Factory function to create subclasses of class_T."""

    class Foo(class_T):
        def fun(self):
            print('%s.fun()' % self.__class__.__name__)

    Foo.__name__ += '_' + class_T.__name__  # rename the subclass to reflect its heritage
    return Foo

class Base1:
    def bar(self):
        print('Base1.bar()')

class Base2:
    def bar(self):
        print('Base2.bar()')

Foo_Base1 = template(Base1)
print('Foo_Base1 base classes: {}'.format(Foo_Base1.__bases__))

Foo_Base2 = template(Base2)
print('Foo_Base2 base classes: {}'.format(Foo_Base2.__bases__))

subclass1 = Foo_Base1()
subclass1.fun()
subclass1.bar()
subclass2 = Foo_Base2()
subclass2.fun()
subclass2.bar()

输出:

Foo_Base1 base classes: (<class __main__.Base1 at 0x00A79C38>,)
Foo_Base2 base classes: (<class __main__.Base2 at 0x00A79DC0>,)
Foo_Base1.fun()
Base1.bar()
Foo_Base2.fun()
Base2.bar()

在这个(名字起得不太好)的 template() 函数中的代码是一个常见的例子,通常被称为 类工厂,或者说是 工厂模式 的一种实现。所以,顺便提一下,你可能会觉得我对这个问题的回答 《什么是类工厂?》 也很有帮助。

编辑: 增加了代码,使每个返回的子类都有不同的类名——这个灵感来自于 @aaronasterling 的一个见解(在一条现在已删除的评论中),提到如果制造的类总是有相同的名字,在调试时可能会造成困惑。

撰写回答