Python 实例,类,静态方法说明
2017-04-18
翻译转载
地址:https://realpython.com/blog/python/instance-class-and-static-methods-demystified/
Python学习群:278529278 (欢迎交流)
总览
首先让我先看一个包含了三种方法的类的例子(Python 3)
class MyClass:
def method(self):
return 'instance method called', self
@classmethod
def classmethod(cls):
return 'class method called', cls
@staticmethod
def staticmethod():
return 'static method called'
实例方法
MyClass类中,第一个方法,mehtod方法就是一个普通的实例方法。这是一个基本的,没有过多修饰的方法类型,在大多数情况下,我们使用的都是这个方法。
实例方法一定有一个self参数,指向这个类的实例本身。通过self参数,类的实例可以自由的获取实例的属性和其他方法。这为在方法内修改实例状态提供了一个入口。
除此之外,实例方法还可以通过 self.__class
属性来访问类,这意味着,实例方法同样可以修改类本身的状态。
类方法
我们再来看@classmethod
装饰器修饰的classmethod方法,类方法一定含有一个cls参数指向类本身。所以类方法只能修改类的状态,而无法修改实例对应的状态。
而类状态会这个类的所有实例共享。
静态方法
静态方法由装饰器@staticmethod
来标识,这种类型的方法即没有self
参数, 也没有cls
参数。因此它无法影响类或者实例的状态,静态方法被严格限制它所能访问的数据。
它可以主要用来限定方法的命名空间(类中)。
实战例子
我们首先来看这些类型的方法在被调用的时候的行为。我们首先创建一个MyClass的实例,使用它来调用三种方法。 三个方法都会返回一个元组,这里面包含了我们想要追踪的信息。 下面是调用实例方法后的返回信息
>>> obj = MyClass()
>>> obj.method()
('instance method called', <MyClass instance at 0x101a2f4c8>)
上面的输出显示,实例方法通过self
参数返回了类的对象实例。当实例方法被调用的时候。Python会用类实例对象obj
替换self
参数。
我们可以忽略掉点操作符的语法糖,手工的将类的实例传入。得到相同的输出。
>>> MyClass.method(obj)
('instance method called', <MyClass instance at 0x101a2f4c8>)
再来看看类方法
>>> obj.classmethod()
('class method called', <class MyClass at 0x101a2f4c8>)
调用类方法返回的是类对象本身,在python中类本身也是一个对象。点操作符的语法糖是和实例方法一样的。这里self
, cls
都是一个命名规范,可以修改为任意名字。
最后我们来看下静态方法
>>> obj.staticmethod()
'static method called'
这里需要注意的是类的实例对象可以直接调用类的静态方法。python在调用静态方法的时候,不传入self
和cls
。
这样静态方法就成了这个类所有实例共享的一个方法,但是它只存在于这个类的命名空间中。
>>> MyClass.classmethod()
('class method called', <class MyClass at 0x101a2f4c8>)
>>> MyClass.staticmethod()
'static method called'
>>> MyClass.method()
TypeError: unbound method method() must
be called with MyClass instance as first
argument (got nothing instead)
当我们尝试使用类对象本身来调用三个方法的时候, 会发现类对象调用实例方法会报出类型异常(TypeError
), 这也是可以遇见的,在调用实例方法的时候,类对象无法为self对象提供实例。
来看一个具体的例子。 我们创建一个Pizza
类:
class Pizza:
def __init__(self, ingredients):
self.ingredients = ingredients
def __repr__(self):
return f'Pizza({self.ingredients!r})'
>>> Pizza(['cheese', 'tomatoes'])
Pizza(['cheese', 'tomatoes'])
美味Pizza的工厂
我们都知道有很多不同口味Pizza,意大利人早在一个实际就给他们的Pizza分好类,所以每种美味的Pizza都有它们的名字。 现在可以使用Pizza这个类制造出他们渴望的Pizza。采用类方法作为工厂函数,我们可以定义不同类型的Pizza的创建方法。
class Pizza:
def __init__(self, ingredients):
self.ingredients = ingredients
def __repr__(self):
return f'Pizza({self.ingredients!r})'
@classmethod
def margherita(cls):
return cls(['mozzarella', 'tomatoes'])
@classmethod
def prosciutto(cls):
return cls(['mozzarella', 'tomatoes', 'ham'])
我们通过cls
参数来构建Pizza实例对象,从而取代直接调用Pizza类的构造函数。这样做的好处是,当我们想要修改类命的时候,我们不需要在修改每个工场方法里的构造函数的名称。
这也符合Don’t Repeat Yourself (DRY) 的原则。 来看下实际的调用结果。
>>> Pizza.margherita()
Pizza(['mozzarella', 'tomatoes'])
>>> Pizza.prosciutto()
Pizza(['mozzarella', 'tomatoes', 'ham'])
通过调用基于类方法的工场方法,我们定制了返回的实例对象。类方法赋予了我们自定义构造方法的能力。这有助类结构的可读性和调用的友好性。
何时使用静态方法
我们来尝试着把Pizza拉的薄一些。
import math
class Pizza:
def __init__(self, radius, ingredients):
self.radius = radius
self.ingredients = ingredients
def __repr__(self):
return (f'Pizza({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
这里我们首先在构造函数参数列表中增加了一个Pizza的半径。 并且增加了一个面积的实例函数(可以被当成一个属性) 并且把计算面积的逻辑独立剥离出来放到了一个静态方法里面。来看看输出结果。
>>> p = Pizza(4, ['mozzarella', 'tomatoes'])
>>> p
Pizza(4, {self.ingredients})
>>> p.area()
50.26548245743669
>>> Pizza.circle_area(4)
50.26548245743669
这里 circle_area
不接受类和实例参数,它本身独立于类和实例对象之外。这样的好处除了避免额外对类或者实例状态修改带来的潜在问题。
同时也是一个良好的编程规范。易于维护和单元测试。这里代码都封装在同一个类中,易于查找和管理。