Python 的实例方法、类方法和静态方法
转载请注明出处:https://blog.youkuaiyun.com/jpch89/article/details/84442713
文章目录
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
不会传递 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. 使用类方法实现披萨工厂
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
。
- 通过实例调用类方法,不用手动传类到
-
静态方法可以用实例或者类调用。
静态方法无法获取到cls
和self
。
静态方法上面要加@staticmethod
装饰器。 -
类方法和静态方法,从某种程度上传达了类的设计意图,使代码易于维护。
完成于 2018.11.24