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在调用静态方法的时候,不传入selfcls。 这样静态方法就成了这个类所有实例共享的一个方法,但是它只存在于这个类的命名空间中。

>>> 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不接受类和实例参数,它本身独立于类和实例对象之外。这样的好处除了避免额外对类或者实例状态修改带来的潜在问题。 同时也是一个良好的编程规范。易于维护和单元测试。这里代码都封装在同一个类中,易于查找和管理。