平凡

python的类方法、实例方法和静态方法

2018/08/23 Share

概览

让我们写一个类,包含类中的三种方法(instance method、class method、static method

class MyClass:
    def method(self):
        return "instance method called", self


    @classmethod
    def classmethod(cls):
        return "class method called",cls

    @ staticmethod
    def staticmthod():
        return "static method called"

print(MyClass().method())
print(MyClass.classmethod())
print(MyClass.staticmthod())

结果:

('instance method called', <__main__.MyClass object at 0x10225a6a0>)
('class method called', <class '__main__.MyClass'>)
static method called

从上面的例子中可以看出:

  • instance method的第一个参数是self
  • classmethod的第一个参数是cls
  • static method对参数没有要求
  • 除了instance method外,其他两类方法都不用实例化。

关于selfcls关键字,还可以这样理解:

  • self指向了一个类的实例
  • cls指向的是类本身

那么,这三种方法有什么区别呢

  • 对于instance method, 这个方法可以随意调用对象中的其他方法;另外它可也可通过self.__class__属性来修改类的状态。
  • class method很明显不能调用对象中的方法(毕竟它不伴随对象)。但是它可以更改类的状态。
  • static method, 这种方法既不能修改对象的状态,也不能修改类的状态。

通过实例来理解这三种方法

再来分析一遍上面运行的结果:

print(MyClass().method())
print(MyClass.classmethod())
print(MyClass.staticmthod())

# result
('instance method called', <__main__.MyClass object at 0x10225a6a0>)
('class method called', <class '__main__.MyClass'>)
static method called

首先来看第一行,instance method,

  • 由于它输出了MyClass object,所以,可以确信它能够访问类的对象实例。
  • 当这个方法被调用,python就会将self参数替换为类的实例

为什么要加selfcls
我们在写python类方法的时候,可能一开始很奇怪,我们的编辑器默认自动为我们添加了第一个参数self
上面classmethod也是自动加了一个cls参数。
其实,selfcls只是约定俗成。你可以随便命名(没想到吧),例如:

def method2(selfhahaha):
    return "instance method called", selfhahaha

@classmethod
def classmethod2(clssssss):
    return "class method was called", clssssss

看到这代码,也许你会眉头一皱,这难道也行? 但结果确实是可以运行的:

print(MyClass().method2())
print(MyClass.classmethod2())

# result:
('instance method called', <__main__.MyClass object at 0x1061eecf8>)
('class method was called', <class '__main__.MyClass'>)

但唯一要注意的是:必须放到第一个参数的位置


好了,现在是时候分析最后一个static method了:
我们这样调用:

obj1 = MyClass()
print(obj1.staticmthod())

结果竟然……可以运行:

static method called

一些人可能是第一次听说,静态方法通过类的对象也是可以调用的
其实在java里,我们也可以这样做,只不过java并不推荐这种调用方式:

public class Solution {


    public static void helloWorld(){
        System.out.println("hello world");
    }


    public static void main(String[] args) {

        Solution solution=new Solution();
        solution.helloWorld();

    }
}

可以看到,static method既没有self参数,也没有cls参数,所以这类方法既不能访问类的状态,也不能访问对象的状态。


现在,大家对这三种方法的区别可能有了一个大致的印象,但是什么时候该使用这些方法呢?


使用@classmethod实现Pizza工厂

首先我们来实现一个pizza类:

class Pizza:
    def __init__(self, ingredients):
        self.ingredients = ingredients

    def __repr__(self):
        return "Pizza({})".format(self.ingredients)

    __str__ = __repr__


print(Pizza(['cheese', 'tomatoes']))

上面代码很简单,传入一个数组ingredients,代表pizza的原材料。返回一个pizza对象。


现实生活中有许多pizza类别,例如:

Pizza(['mozzarella', 'tomatoes'])
Pizza(['mozzarella', 'tomatoes', 'ham', 'mushrooms'])
Pizza(['mozzarella'] * 4)

所以,可以对上面的类进行改进,使它可以更好的提供pizza对象。

一个很简单也是很有效的方法是:使用classmethod来当作一个工厂方法来产生不同的pizza(工厂模式是设计模式里的一种,具体原理很简单就不介绍了)。

class Pizza:
    def __init__(self, ingredients):
        self.ingredients = ingredients

    def __repr__(self):
        return "Pizza({})".format(self.ingredients)

    __str__ = __repr__

    @classmethod
    def margherita(cls):
        return cls(['mozzarella', 'tomatoes'])

    @classmethod
    def prosciutto(cls):
        return cls(['mozzarella', 'tomatoes', 'ham'])

现在,我们就可以使用这些工厂方法来产生不同类型的Pizza对象了:

>>> Pizza.margherita()
Pizza(['mozzarella', 'tomatoes'])

>>> Pizza.prosciutto()
Pizza(['mozzarella', 'tomatoes', 'ham'])

很显而易见的一个优点是:我们不需要记住乱七八糟的ingredients,只需要报pizza的名字就可以。这跟我们去餐饮点菜是一样的,直接报菜名(蒸羊羔儿、蒸熊掌、蒸鹿尾儿、烧花鸭、烧雏鸡、烧子鹅、炉猪、炉鸭、酱鸡、腊肉、松花、小肚儿、酱肉、香肠、……),但谁会记住它们的原材料是什么呢?


python每个类只有一个__init__初始化方法,但是通过class method,我们神奇的发现,竟然实现了多种多样的构造函数,这让我们的类更加易用。(这就像女朋友只能有一个,但是通过class method,竟然让你找到了一堆xx呢)


什么时候使用static method

现在将上面的类更改一下,加入半径radius这个参数,再写一个求面积的方法:

class Pizza:
    def __init__(self, ingredients, radius):
        self.ingredients = ingredients
        self.radius = radius

    def __repr__(self):
        return "Pizza({})".format(self.ingredients)

    __str__ = __repr__

    def area(self):
        return self.circle_area(self.radius)

    @staticmethod
    def circle_area(r):
        return 3.14 * r ** 2

将求面积的方法单独写到了circle_area中。
测试:

p = Pizza(['mozzarella', 'tomatoes'], 4)
print(p.area())

# result:
50.24

这个例子很简单,但能帮助我们理解static method的优点。
在上面介绍过,static method既不能访问类,又不能访问对象,因为它没有clsself参数。但正因如此,它具有独立的优点。
为什么这样说呢?
它避免了在开发过程中对程序的改动(我们知道,程序员代码都是写一天调一个星期、甚至一个月,初始的架构可能后面改的体无完肤)
在测试时,static method也具有很大优点。因为它与类无关,我们并不需要初始化一个对象,所以更容易测试。这也为后期的维护带来方便。


参考并翻译自:链接

发表日期: August 23rd 2018

版权声明: 本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG
  1. 1. 概览
  2. 2. 通过实例来理解这三种方法
  3. 3. 使用@classmethod实现Pizza工厂
  4. 4. 什么时候使用static method