您的位置首页生活百科

Python 中的 classmethod 和 staticmethod 有什么具体用途?

Python 中的 classmethod 和 staticmethod 有什么具体用途?

的有关信息介绍如下:

Python 中的 classmethod 和 staticmethod 有什么具体用途?

没看到特别全面的回答。

Python中的类也是一个普通对象,如果需要直接使用这个类,例如将类作为参数传递到其他函数中,又希望在实例化这个类之前就能提供某些功能,那么最简单的办法就是使用classmethod和staticmethod。这两者的区别在于在存在类的继承的情况下对多态的支持不同。

所以本质上来说,面向对象中实例方法有哪些作用,classmethod也就有哪些作用,只是这个面向的“对象”是类本身而已。这和C++中的static method其实是不同的,C++中的static method只有命名空间的作用,而Python中不管是classmethod还是staticmethod都有OOP、多态上的意义。

这里不算上因为个人审美原因,希望将外部函数放到类里面的情况,只考虑必须通过类访问的情形

类最基本的作用是实例化出一个对象,但是有的时候再实例化之前,就需要先和类做一定的交互,这种交互可能会影响实际实例化的过程,所以必须放在调用构造函数之前。

举例来说,我们的应用有一个插件系统,可以自动从插件目录中加载外部插件并调用。这是一个典型的依赖倒置的场景,典型的代码如下:

Plugin是用户自定义插件的基类,它的构造函数接受一个api_interface的参数,将它保存到实例中。接下来,我们的应用会在合适的event出现时调用plugin的callback接口,实现通过插件扩展功能的目标。

随着业务的发展,在应用的第二版,我们重新设计了插件系统:

那么基于这样的需求,我们可以发现,我们必须在构造plugin对象之前,就先从类中获取到一定的信息。使用issubclass判断类的基类可行,但并不好:我们不应当强求用户插件从我们给定的基类派生,而只要遵循相同的接口即可。这种情况下的解决方法之一就是通过classmethod获得类的信息。

我们可以设计出以下的代码:

可以看到,PluginV2有个capabilites()的classmethod,插件系统在初始化插件之前,首先会调用这个类的方法,通过这个方法的信息判断要如何初始化插件实例。

在这个例子中,由于plugin一般是单例的,所以不使用classmethod的变通的方法也可以是先不带参数初始化一个对象,然后将capabilities()转化为实例方法,再额外增加一个initialize()方法做二次初始化,这是Java中Spring等框架常用的方法,但它其实并不如上面的代码清晰直观。

对于非单例的情况也有很好的例子:

上面的设计中,我们试图通过插件的方式支持各类数据库作为底层数据库存储,插件只需要实现DBCursor的子类即可。在save_to_database时,系统查找合适的插件类,通过这个类创建新的DB cursor,然后通过cursor将数据保存到数据库中。但是,一般数据库插件都支持很多的配置,我们希望这个配置可以以集中式的方式保存在应用配置中,这样我们为DBCursor类增加了一个configure接口,它会在任何DB Cursor被实例化之前,首先在类上被调用,这样在初始化__init__的时候,就可以使用这个配置了。

在Java等语言中,这类功能通常通过工厂类(Factory)实现,先初始化一个工厂类的实例,然后由这个工厂类的实例构造实际需要的实例。在Python中,普通类完全可以替代Factory,而对于支持配置的Factory,就对应到相应的classmethod。

2. 特殊构造函数

常举的例子包括Date.parse_from_string之类。这里也举一个比较实际的例子:当我们要将一个对象序列化成数据,再从数据中重新恢复时,一般来说,可以将序列化的方法作为instancemethod,而将恢复的方法作为classmethod,原因在于恢复时不一定经过普通的__init__,这样恢复过程可以看作是一种特殊的构造过程

上面的代码中,我们规定一种序列化协议,它的对象有serialize方法,而相应的类有deserialize方法。保存到文件时,首先将类的信息写入文件,然后写入serialize的返回结果;加载时,通过类的信息找到相应的类,然后调用类的deserialize方法,得到原始的类。这种方法很简单,扩展起来也非常灵活。如果不使用classmethod,一般来说需要引入一个独立的Serializer类,通过Serializer类进行序列化和反序列化,比较麻烦。通过附加在类上的特殊构造函数则省掉了这个麻烦而且让代码更清晰。

3. __new__等

众所周知,__new__的特殊之处在于它永远是一个实质上的classmethod(准确来说是staticmethod,第一个参数需要手工传递cls),而不需要classmethod声明。它是Python的类系统的hook点,可以用来改变构造类时创建对象的过程。同类的magic函数还有一些,一般的应用设计中不常用,这里不再介绍。

一种单例模式的实现重写了__new__,使得它总是返回同一个实例(代码略)

4. 为以上函数提供子类hook点

显然,classmethod只能够调用其他classmethod。因此,如果我们希望在某个classmethod中,留出一个子类型可以重写的部分,就需要将这个可以重写的hook点定义为classmethod。可以看下面的例子:

这是我们前面的PluginV2版本的例子的改进,在这个实现中,我们将API版本和返回events拆到了不同的classmethod中,在capabilities中分别调用这两个函数实现。这样,在子类当中,可以简单地重写这两个方法来修改capabilities的返回,而不需要整个重写capabilities方法。这和实例方法的原理是一样的,只是在这里是用在类本身上面而已。

这两个方法的用法是类似的,在上面的例子中大多数情况下,classmethod也可以通过staticmethod代替,在通过类调用时,这两者对于调用者来说是不可区分的。

这两者的区别在于,classmethod增加了一个对实际调用类的引用,这带来了很多方便的地方:

一般来说classmethod可以完全替代staticmethod。staticmethod唯一的好处是调用时它返回的是一个真正的函数,而且每次调用时返回同一个实例(classmethod则会对基类和子类返回不同的bound method实例),但这点几乎从来没有什么时候是有用的。不过,staticmethod可以在子类上被重写为classmethod,反之亦然,因此也没有必要提前将staticmethod全部改为classmethod,按需要使用即可。

classmethod与类的metaclass的实例方法是等效的,classmethod优先级高一些,上面的大多数例子也可以改用metaclass。

使用metaclass的优点在于:

使用classmethod的优点在于:

建议:

Python中的classmethod(和staticmethod)并不止拥有美学上(或者命名空间上)的意义,而是可以实际参与多态的、足够纯粹的OOP功能,原理在于Python中类可以作为first class的对象使用,很大程度上替代其他OOP语言中的工厂模式。classmethod既可以作为factory method提供额外的构造实例的手段,也可以作为工厂类的接口,用来读取或者修改工厂类本身。classmethod还可以通过额外的类引用,提供继承时的多态特性,实现子类挂载点等。