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 参数,该实例的类未必是定义类方法的类。如下例:
'''
学习中遇到问题没人解答?小编创建了一个Python学习交流QQ群:531509025
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
# 父类
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 不会传递 self 和 cls ,以此来限制静态方法的权限。所以静态方法不能获取实例或者类的状态。 它们就像普通函数一样,只不过隶属于类和该类的每个实例的命名空间。
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. 使用类方法实现披萨工厂
'''
学习中遇到问题没人解答?小编创建了一个Python学习交流QQ群:531509025
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
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 类。
'''
学习中遇到问题没人解答?小编创建了一个Python学习交流QQ群:531509025
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
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 。
静态方法可以用实例或者类调用。
静态方法无法获取到 cls 和 self 。 静态方法上面要加 @staticmethod 装饰器。
类方法和静态方法,从某种程度上传达了类的设计意图,使代码易于维护。