Python 的实例方法、类方法和静态方法|8月更文挑战

942 阅读6分钟

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


0. 参考资料


1. 概览

先定义一个最简单的 Python 3 的类:

class MyClass:
    def method(self):
        print('我是实例方法', self)

    @classmethod
    def classmethod(cls):
        print('我是类方法', cls)

    @staticmethod
    def staticmethod():
        print('我是静态方法')

1.1 实例方法

第一个方法 method(self) 方法是实例方法 instance method。 当 method 被调用时,self 参数指向 MyClass 类的一个实例。 实例方法可以通过 self 自由地访问同一对象的属性和其它方法,这样它们可以修改实例的状态。 注意实例方法可以通过 self.__class__ 属性来获取到类,所以实例方法也可以更改类的状态。


1.2 类方法

第二个方法 classmethod(cls)类方法 class method。 上面需要写一个 @classmethod 装饰器。 类方法接收一个 cls 参数,当该方法被调用的时候,它指向类(而不是类的实例)。 类方法只有 cls 参数,所以它不能修改实例的状态。 修改实例的状态必须要有 self 参数。 类方法只能修改类的状态,类状态的更改会作用于所有该类的实例。


1.3 静态方法

第三个方法 staticmethod()静态方法 static method。 它上面要有一个 @staticmethod 装饰器。 静态方法不能修改类或者实例的状态,它受限于它所接收的参数。 我们一般用这种方法来隔离命名空间。


2. 实际应用

2.1 调用实例方法

首先创建一个实例,然后调用一下实例方法:

obj = MyClass()

# 调用实例方法
obj.method()
"""
我是实例方法 <__main__.MyClass object at 0x00000213E209B898>
"""

还可以这样调用:

MyClass.method(obj)
"""
我是实例方法 <__main__.MyClass object at 0x00000213E209B898>
"""

使用 对象.实例方法() 这种点号调用的形式是一个语法糖,Python 会自动把 对象 作为第一个实参,传递给 实例方法 中的 self 形参。 如果使用 类.实例方法(对象) 这种形式,则必须手动传递 对象实例方法 的第一个参数 self

如果不创建实例就调用实例方法,或者是不传入 对象,那么就会出错:

# 不创建实例就调用实例方法会发生什么?
# 会提示缺少位置参数 self
# 实例方法依赖于实例而存在
MyClass.method()
"""
Traceback (most recent call last):
  File "test.py", line 28, in <module>
    MyClass.method()
TypeError: method() missing 1 required positional argument: 'self'
"""

实例方法可以通过 self.__class__ 访问到类。

# 打印类名
print(obj.__class__.__name__)
"""
MyClass
"""

2.2 调用类方法

下面来调用一下类方法。

# 通过类名调用类方法
MyClass.classmethod()
# 也会自动传递类名作为第一个参数
"""
我是类方法 <class '__main__.MyClass'>
"""

通过 类.类方法() 的形式调用类方法,Python 会自动把 作为第一个参数传递给 类方法 的第一个参数 cls,我们不用手动传递。

也可以用实例调用类方法:

# 当然也可以通过实例调用类方法
obj.classmethod()
"""
我是类方法 <class '__main__.MyClass'>
"""

通过实例调用类方法,Python 会把该实例的类传递给 类方法cls 参数,该实例的类未必是定义类方法的类。如下例:

# 父类
class Animal:
    @classmethod
    def classmethod(cls):
        print('cls是:' + str(cls.__name__))


# 子类
class Dog(Animal):
    pass


dog = Dog()
dog.classmethod()
"""
cls是:Dog
"""
# 注意不是类方法的定义类:Animal
# 而是实例的所属类:Dog


2.3 调用静态方法

最后调用一下静态方法:

# 调用静态方法
obj.staticmethod()
"""
我是静态方法
"""
# 调用静态方法的时候
# 点号语法不会自动传递任何参数

通过 实例.静态方法() 调用静态方法的时候,Python 不会传递 selfcls,以此来限制静态方法的权限。所以静态方法不能获取实例或者类的状态。 它们就像普通函数一样,只不过隶属于类和该类的每个实例的命名空间。


2.4 不创建实例调用方法

不创建实例,调用实例方法、类方法和静态方法。

# 不创建实例,调用类方法
MyClass.classmethod()
"""
我是类方法 <class '__main__.MyClass'>
"""

# 不创建实例,调用静态方法
MyClass.staticmethod()
"""
我是静态方法
"""

# 不创建实例,调用实例方法
MyClass.method()
"""
Traceback (most recent call last):
  File "test.py", line 85, in <module>
    MyClass.method()
TypeError: method() missing 1 required positional argument: 'self'
"""

不创建实例,调用实例方法出错。 这是可以理解的,因为我们直接通过类这个蓝图 blueprint 本身来调用实例方法,Python 无法给 self 传参。


3. 使用类方法实现披萨工厂

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

    def __repr__(self):
        return f'披萨({self.ingredients})'

    @classmethod
    def margherita(cls):
        return cls(['马苏里拉奶酪', '番茄'])

    @classmethod
    def prosciutto(cls):
        return cls(['马苏里拉奶酪', '番茄', '火腿'])

使用类方法作为工厂函数,生产不同种类的披萨。

【注】 工厂函数 factory function 工厂函数是一个函数,它根据不同的输入,新建并返回不同的对象。

注意在工厂函数中,没有直接使用 Pizza 这个类名,而是使用了 cls 这个参数。 这样的好处在于易于维护。 万一以后要把 Pizza 这个类名改成 披萨,只改动一处就行,因为类方法中用的是 cls 而不是直接写 类名。 这是遵循 DRY 原则的一个小技巧(Don't repeat yourself

现在使用工厂函数来生成几个披萨吧:

pizza1 = Pizza.margherita()
print(pizza1)
"""
披萨(['马苏里拉奶酪', '番茄'])
"""

pizza2 = Pizza.prosciutto()
print(pizza2)
"""
披萨(['马苏里拉奶酪', '番茄', '火腿'])
"""

我们可以使用工厂函数来创建事先配置好的 Pizza 对象。 这些工厂函数内部都使用了 __init__ 构造函数,它们提供了一个捷径,不用记忆各种披萨配方。 从另外一个角度来看,这些类方法可以为一个类定义多个构造函数


4. 何时使用静态方法

改写上面写的 Pizza 类。

import math


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

    def __repr__(self):
        return (f'披萨({self.radius!r}),'
                f'{self.ingredients!r})')

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

    @staticmethod
    def circle_area(r):
        return r ** 2 * math.pi

试一试使用静态方法:

# 生成披萨
p = Pizza(4, ['马苏里拉奶酪', '番茄'])
print(p)
"""
披萨(4,['马苏里拉奶酪', '番茄'])
"""

# 计算披萨的面积
p.area()
print(p.area())
"""
50.26548245743669
"""

# 通过类调用静态方法
print(Pizza.circle_area(4))
"""
50.26548245743669
"""

# 通过对象调用静态方法
print(p.circle_area(4))
"""
50.26548245743669
"""

把一个方法写成静态方法的好处:

  • 表明它不会更改类或者实例的状态
  • 更容易写测试代码,不用进行实例化就可以测试静态方法

5. 总结

  • 调用实例方法,需要一个实例。实例方法可以通过 self 来获取实例。

    • 通过实例调用实例方法,不用手动传实例到 self
    • 通过类调用实例方法,需要手动传实例到 self
  • 类方法可以用实例或者类来调用。类方法可以通过 cls 获取类本身。类方法上面要加 @classmethod 装饰器。

    • 通过实例调用类方法,不用手动传类到 cls。 通过实例调用的类方法,Python 自动传递到 cls 的类是该对象的所属类,不一定是定义该类方法的类。(比如父类定义了类方法,子类继承父类。通过子类的实例调用父类的类方法,传到 cls 中的参数是子类,而不是定义类方法的父类。)
    • 通过类调用类方法,也不用手动传类到 cls
  • 静态方法可以用实例或者类调用。 静态方法无法获取到 clsself。 静态方法上面要加 @staticmethod 装饰器。

  • 类方法和静态方法,从某种程度上传达了类的设计意图,使代码易于维护。


完成于 2018.11.24