Python面试题系列之13 类方法、实例方法、静态方法

Python面试题系列之13: 类方法、实例方法、静态方法

Question

请简述下Python 中类方法实例方法静态方法有何区别?

知识点详解

在Python的类中,实例方法(instance method),类方法(class method)与静态方法(static method)经常容易混淆,本文中通过几个例子逐一介绍下。

实例方法·instance method

Python的实例方法,用得最多也最为常见。先来看这个例子。

class MyCar:
    def __init__(self, color):
        self.color = color

    def car_color(self):
        print(self.color)

mycar1 = MyCar('red')

调用一下看看

>>> mycar1.instance_m()
red

上述例子中,car_color为一个实例方法。实例方法第一个参数为self,当使用mycar1.car_color()调用实例方法时,实例mycar1会传递给self参数,这样self参数就可以引用当前正在调用实例方法的实例。

类方法·class method

Python 的类方法,采用装饰器@classmethod来定义,我们来看这个例子。

class MyCar:
    num = 0
    def __init__(self):
        MyCar.num += 1

    @classmethod
    def car_num(cls):
        return cls.num

car1 = MyCar()
car2 = MyCar()

我们调用一下看看

>>> MyCar.car_num()
2 

这个例子中我们来统计类MyCar实例的个数。于是定义了一个类变量num来存放,然后我们通过装饰器@classmethod的使用,方法car_num被定义成一个类方法。在调用类方法时,Python会将类(class MyCar)传递给cls,这样在car_num内部就可以引用类变量num。
由于在调用类方法时,只需要将类型本身传递给类方法,因此,我们既可以通过类、也可以通过实例,来调用类方法。

静态方法·static method

Python中还有一种方法,采用装饰器@staticmethod来定义,我们称之为静态方法。先来看看这个例子。

我们定义一个“三角形”的类,通过传入3条边的长度来构造出三角形,并提供计算周长的方法。

from math import sqrt

class Triangle(object):
    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    def perimeter(self):
        return self._a + self._b + self._c

但是小学数字老师告诉我们,任意给的三条边长未必能构造出三角形。三条边是需要满足一定条件的:

(a + b > c) and (b + c > a) and (a + c > b)

所以我们可以先写一个方法,来验证三条边长是否可以构成三角形。

def is_valid(a, b, c):
    return a + b > c and b + c > a and a + c > b

这个方法很显然就不是对象方法,因为在调用这个方法时三角形对象尚未创建出来(因为都不知道三条边能不能构成三角形),即这个方法应属于三角形类,但并不属于三角形对象的。这就用到了我们的静态方法。具体代码如下:

class Triangle(object):
    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    @staticmethod
    def is_valid(a, b, c):
        return a + b > c and b + c > a and a + c > b

    def perimeter(self):
        return self._a + self._b + self._c

然后我们给定三个边长,并计算由其组成的三角形的周长。

a, b, c = 6, 7, 8

if Triangle.is_valid(a, b, c):
    t = Triangle(a, b, c)
    print(t.perimeter())
else:
    print('无法组成三角形')

我们介绍完这三种方法定义及使用场景的区别,接下来我们看看它们在调用时的差异。

调用上的区别
还是通过一个例子来说明,这里定义一个MyClass类,同时定义了实例、类、静态三种方法。

class MyClass(object):
    def instance_m(self, x):
        print(f'executing instance_m({self}, {x})')

    @classmethod
    def class_m(cls, x):
        print(f'executing class_m({cls}, {x})')

    @staticmethod
    def static_m(x):
        print(f'executing static_m({x})')

mc = MyClass()

接下来我们调用这三种实例,分析下输出的结果

# 调用实例方法
mc.instance_m(666)
print(mc.instance_m)
print('-----------------------------------------')
# 调用类方法
mc.instance_m(666)
MyClass.class_m(666)
print(mc.class_m)
print('-----------------------------------------')
# 调用静态方法
mc.static_m(666)
MyClass.static_m('人人都是Pythonista')
print(mc.static_m)

以下是执行结果

executing instance_m(<__main__.MyClass object at 0x0000023DA63E7E48>, 666)
<bound method MyClass.instance_m of <__main__.MyClass object at 0x0000023DA63E7E48>>
-----------------------------------------
executing class_m(<class '__main__.MyClass'>, 666)
executing class_m(<class '__main__.MyClass'>, 666)
<bound method MyClass.class_m of <class '__main__.MyClass'>>
-----------------------------------------
executing static_m(666)
executing static_m(人人都是Pythonista)
<function MyClass.static_m at 0x0000023DA63E6048>

对于实例方法,调用时会把实例mc作为第一个参数传递给self参数。因此,调用mc.instance_m(666)时输出了实例mc的地址。

对于类方法,调用时会把类MyClass作为第一个参数传递给cls参数。因此,调用mc.instance_m(666)时输出了MyClass类型信息。 前面提到,可以通过类也可以通过实例来调用类方法,在上述代码中,我们再一次进行了验证。

对于静态方法,调用时并不需要传递类或者实例。其实,静态方法很像我们在类外定义的函数,只不过静态方法可以通过类或者实例来调用而已。

上面这个例子,我们发现类方法、静态方法都可以通过类直接调用,那么实例方法呢?

>>> MyClass.instance_m(666)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-23-082125d2908b> in <module>
----> 1 MyClass.instance_m(666)

TypeError: instance_m() missing 1 required positional argument: 'x'

实例方法不能通过类直接调用,它需要一个实例绑定,比如写成如下形式:

>>> MyClass.instance_m(mc, 666)
executing instance_m(<__main__.MyClass object at 0x0000023DA63E7E48>, 666)

从上面的例子,我们还可以看到:

实例方法instance_m与实例mc进行了绑定。

<bound method MyClass.instance_m of <__main__.MyClass object at 0x0000023DA63E7E48>>

类方法class_m与类MyClass进行了绑定。

<bound method MyClass.class_m of <class '__main__.MyClass'>>

静态方法static_m并不会与类MyClass或者实例mc绑定。

<function MyClass.static_m at 0x0000023DA63E6048>

Answer

实例方法Instance methods,需要一个类实例,并且可以通过self访问实例。
只有实例对象可以调用。

类方法Class methods,类方法不是绑定到对象上,而是绑定在类上的方法。不需要类实例。他们无法访问实例,但它们可以通过cls访问类本身。
在定义时需在上方使用@classmethod进行装饰,类对象和实例对象都可调用。

静态方法Static methods,在类中无需实例参与即可调用,无权访问clsself。它们像常规函数一样工作,但属于类的命名空间。
在定义时需在其上方使用@staticmethod进行装饰,类对象和实例对象都可调用。

后记

以上就是Python中的实例方法、类方法与静态方法的区别,在面试时可以从定义及调用时差异入手,来解释这三种方法。欢迎大家在评论区留言说出自己的看法。好了,以上就是本篇全部内容。

备注:本篇首发于知识星球「人人都是Pythonista」。


文章作者: &娴敲棋子&
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 &娴敲棋子& !
评论
  目录