python学习系列--staticmethod和classmethod

来源:互联网 发布:口袋妖怪能力数据 编辑:程序博客网 时间:2024/05/17 01:58


1. 在python类中有两个装饰器,staticmethod和classmethod, 它们的作用似乎明确又模糊。由于缺乏使用场景,我们对这两个装饰器(功能)的理解总是不够深入,不够全面。本文试图对它们进行一次全方位的考查,进而对它们的使用有一些更好的体会。

本人水平比较渣,以上良好的愿望应该只能是个愿望,这篇文章有很多不完善的地方,请各位读者不吝指出。


2. 鸭子测试(Duck Test)是对一种归纳推理方式的幽默的说法,它可以解释为:

如果它看起像鸭子,叫起来像鸭子,游起来像鸭子,那么它可能就是鸭子。

这个测试表明人能够通过观察未知事物的明显外在特征来推断该事物的本质。有时被用来解决看似深奥但其实并不深奥的问题。

很多时候,我们知道有一个事物,我们知道它的的名字,知道它出现在一些场合,但是我们并不明白它到底是怎么回事。这往往是由于我们对它的特征不清楚。通过观察这个事物的行为特征,或许我们就能逐渐了解它。


3. 实例方法。通常,在python类中定义的普通方法就是实例方法。它的语法如下:

class C(object):    def f(self, arg1, arg2, ...):        ...
实例方法的参数中有一个必不可少的参数self,它必须位于参数列表的第一个位置,它相当于该方法所属的类在实例化后的实例本身(Instance),表示传入该方法所属类的一个实例(Python2中必须是该类的实例,Python3中可以是任何东西)。实例方法被调用时,参数self被隐式地传入方法中,无需显式指定。

既然叫做实例方法,那么它必须使用该类的实例才能调用( C().f() ),直接使用类来调用该方法是无效的( C.f() )。


4. staticmethod(静态方法),需要一个装饰器语法(@staticmethod)将一个普通方法(实例方法)转换为静态方法:

class C(object):    @staticmethod    def f(arg1, arg2, ...):        ...
staticmethod 方法的参数是自定义的任何参数,当然也可以没有参数。它不需要一个隐含参数,像self这样的。这是它与实例方法和类方法之间在形式上的一个主要不同之处。

staticmethod即可以在类上调用(C.f())也可以在实例上调用(C().f()). 在实例上调用时,实例被忽略而类被使用---实际上还是类调用。

staticmethod不需要自身对象self和自身类的cls参数,没有对类或实例的依赖,它处理与类无关的信息,就像恰好放在类中的一个局部函数,但是它的功能又与类有某种关联。

如果需要在staticmethod中调用类的属性和方法,可以直接用类名.属性名/类名.方法名。


5. classmethod(类方法),需要一个装饰器语法(@classmethod)将一个普通方法(实例方法)转换为类方法:

class C(object):    @classmethod    def f(cls, arg1, arg2, ...):        ...
classmethod需要接收一个隐含参数作为第一个参数,不同于实例方法的self, 类方法的隐含参数叫做cls(其实可以叫任何别的名字,这里主要是因为命名惯例), 表示传入该方法所属的类。 

classmethod即可以在类上调用(C.f())也可以在实例上调用(C().f()). 在实例上调用时,实例被忽略而类被使用---实际上还是类调用。

如果classmethod是在派生类中被调用的,被隐式传入cls参数的是那个子类对象,而不是父类。


6. 应用举例。下面通过代码列举了staticmethod和classmethod的一些使用场合。

  6.1 基本使用。定义一个实例方法,静态方法和类方法,并分别进行调用。

class A(object):    def im(self):        print 'Instance method: ', self    @classmethod    def cm(cls):        print 'Class method: ', cls        return cls    @staticmethod    def sm():        print 'Static method.'
  调用过程如下:

>>> A().im()在实例上调用实例方法Instance method:  <A.A object at 0x7f95dfb19210>>>> A().sm() 在实例上调用静态方法Static method.>>> A().cm() 在实例上调用类方法Class method:  <class 'A.A'>>>> A.im() 在类上调用实例方法,出错  Traceback (most recent call last):  File "<stdin>", line 1, in <module>TypeError: unbound method im() must be called with A instance as first argument (got nothing instead)>>> A.sm() 在类上调用静态方法Static method.>>> A.cm() 在类上调用类方法Class method:  <class 'A.A'>


  6.2  在派生类中使用父类的类方法。

class B(A):pass 

调用如下:  

>>> B.cm()在子类中调用父类的类方法

Class method:  <class '__main__.B'>显示的cls参数值为子类对象


6.3 将类方法用于工厂方法。这时 classmethod 的作用实际上是创建了类的实例, 而在这之前可以做一些预处理,比如设置变量信息。在下面的例子中,如果使用 @staticmethod代替,那我们不得不硬编码Pizza类名在函数中,这使得任何继承Pizza的类都不能使用我们这个工厂方法给它自己用。

class Pizza(object):
    def __init__(self, ingredients):
        self.ingredients = ingredients
 
    @classmethod
    def from_fridge(cls, fridge):
        return cls(fridge.get_cheese() + fridge.get_vegetables())


6.4  调用静态方法。如果你想其他的方法中调用类中的 staticmethod, 你觉得应该怎么样?classname.staticmethod? 这样岂不是要将类名硬编码?更好的办法是在 classmethod 中使用 cls.staticmethod. 在下面的例子中,Volume类名永远都不会被直接使用,继承和方法覆盖都可以完美地工作。

class Volume(object):
    def __init__(self, radius, height):
        self.radius = radius
        self.height = height
 
    @staticmethod
    def compute_area(radius):
         return math.pi * (radius ** 2)
 
    @classmethod
    def compute_volume(cls, height, radius):
         return height * cls.compute_area(radius)
 
    def get_volume(self):
        return self.compute_volume(self.height, self.radius)

7.  现在我们已经看到了一些使用 staticmethod 和 classmethod 的例子。结合这些例子,我们再来进一步总结一下这两者的特点。

staticmethod 不需要任何类和实例的信息。 classmethod 需要类的信息。 普通方法需要实例信息。
一般情况下,要使用某个类的方法,需要先实例化一个对象再调用方法。而使用 @staticmethod 或 @classmethod,就可以不用构造实例,直接使用 classname.methodname() 来调用。
staticmethod 可以被用来组织类之间有逻辑关系的函数。在很多情况下,一些函数与类相关,但不需要任何类或实例变量就可以实现一些功能。比如设置环境变量,修改另一个类的属性等等。假如我们想仅实现类之间的交互而不是通过实例,我们可以在类之外建立一个简单的函数来实现这个功能,但是这会使代码扩散到类之外,可能对未来代码维护产生问题。

python 中使用工厂模式(alternative constructor)是最典型的使用 classmethod 的场景。
classmethod尤其适合用在当我们需要在创建真正的类实例之前做一些预设置的情况下,因为实例建立之前显然你是不能使用实例方法的,你只能使用classmethod.
这样做的另一个好处就是你以后重构类的时候不必要修改构造函数,只需要额外添加你要处理的函数,然后使用装饰符 @classmethod 就可以了。相当于我们拥有了多样化的构造函数。

Python使用类时,通常需要为对象初始化绑定方法,绑定方法同样是对象,但是创建他们需要成本,而staticmethod就可以避免这些。
staticmethod 和 classmethod 有效地进行了代码的隔离,有利于组织代码,有利于命名空间的整洁。
classmethod 因为持有cls参数,可以调用类的属性,类的方法等,避免了使用类名硬编码。
classmethod 既具有普通实例方法访问类名称空间的能力,又具有静态方法随意使用的灵活性。

实例属性由实例更改,不会影响类属性。而类属性则可以由类方法 (classmethod) 来更改。


8. Reference

http://python.jobbole.com/81595/
https://docs.python.org/2/library/functions.html#staticmethod
http://www.wklken.me/posts/2013/12/22/difference-between-staticmethod-and-classmethod-in-python.html
https://stackoverflow.com/questions/12179271/meaning-of-classmethod-and-staticmethod-for-beginner
https://stackoverflow.com/questions/136097/what-is-the-difference-between-staticmethod-and-classmethod-in-python