Python类学习-静态方法与类方法

刚刚拜读了一篇在 python 类中:

静态类 @staticmethod 和类方法 @classmethod 两个装饰器的基本使用场景。

收获良多,详细的分析可以阅读原文1

本文记录下使用静态类和类方法,在使用的细节,以及自己一些思考。

一、静态类的定义与使用

在学习 @staticmethod 之前,一直都是用『私有方法』来表示『静态方法

定义静态类

如代码所示的_add_two_string_num 方法,就是『私有方法』:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def _add_two_string_num(self, a, b):
        """在方法名前加一个下划线,用以表示私有方法"""
        a_int = int(a)
        b_int = int(b)
        return a_int + b_int
    
    def calc_age_after_n_year(self, n):
        age = self.add_two_string_num(self.age, n)
        print(f'{n}年以后,我{age}岁')
# 使用
>>> corejk = Person("core", "20")
>>> corejk.calc_age_after_n_year(10)
>>> 10年后我30岁
    

用『静态类』改写一下,然后对比执行结果

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    @staticmethod
    def add_two_string_num(a, b):
       """注意这里参数去掉了self"""
        a_int = int(a)
        b_int = int(b)
        return a_int + b_int
    
    def calc_age_after_n_year(self, n):
        # 注意这里把 self 替换成了 Person 类名
       age = Person.add_two_string_num(self.age, n)
        print(f'{n}年以后,我{age}岁')
# 使用
>>> corejk = Person("core", "20")
>>> corejk.calc_age_after_n_year(10)
>>> 10年后我30岁

可以看到代码执行结果还是一样 ,分析一些注释部分发生了什么:

​ ① 我在 add_two_string_num 方法的下划线,然后再头上戴了一个静态方法的『@staticmethod』的帽子,并去掉了self形参

​ ② 戴上静态方法的帽子后,add_two_strinig_num 调用方法由 self.xxx 变成了Person.xxx, 使用类名调用该方法。

使用场景

仔细观察add_two_string_num这个静态类方法,可以发现它和 Person 这个类表示『人』这个抽象概念,没有什么关系(只是将两个字符串转成整型后相加)。

如果代码的其他类也会用到它,可以把它单独放到 Person 类外面,作为一个『工具函数』;

如果这个『工具函数』只有某一个类会用到,那么最好给它带上@staticmethod的帽子(装饰器)。

一句话总结:静态方法就是某个类专用的工具函数。

二、类方法的定义与使用

此『类方法』非彼『类的方法』,注意概念的区分

定义类方法

import re

class People:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def introduce_myself(self):
        print(f'大家好,我叫: {self.name}')

    @staticmethod
    def add_two_string_num(a, b):
        ... skip ...

   @classmethod
   def from_chinese_string(cls, sentence):
        name = re.search('名字:(.*?),', content).group(1)
        age = re.search('年龄:(\d+)', content).group(1)
       return cls(name, age)

# 使用        
  >>> content = '我的名字:coreJK,我的年龄:20,把它提取出来'
 >>> corejk = People.from_chinese_string(content)
  >>> corejk.introduce_myself()
  >>> 大家好, 我叫: corejk

​ ①『类方法』要戴上@classmethod的帽子,而定义一个普通的『类的方法』不用;

​ ②『类方法』除了要戴帽子,self也要换成cls,这个参数其实就是People这个类本身;

​ ③ 这里相当于People(name, age),但是还未实例化,并返回;

​ ④ 在初次实例化People时,调用了类方法from_chinese_string,利用正则匹配__init__需要的name、age参数,完成;

这样做有什么好处呢?好处就在于我们完全不需要修改__init__

那么,也就不需要修改代码里面其它调用了People类的地方。

例如现在我又想增加从英文句子里面提取名字和年龄的功能,那么只需要再添加一个类方法就可以了:

import re

class People:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def introduce_myself(self):
        print(f'大家好,我叫: {self.name}')

    @staticmethod
    def add_two_string_num(a, b):
        ... skip ...

    @classmethod
    def from_chinese_string(cls, sentence):
        name = re.search('名字:(.*?),', content).group(1)
        age = re.search('年龄:(\d+)', content).group(1)
        return cls(name, age)
	@classmethod
    def from_english_string(cls, sentence):
        name = re.search('name: (.*?),', content).group(1)
        age = re.search('age: (\d+)', content).group(1)
        return cls(name, age)
        

# 使用        
>>> content = 'My name:coreJK,My age:20,hello'
>>> corejk = People.from_chinese_string(content)
>>> corejk.introduce_myself()
>>> 大家好, 我叫: coreJK

使用场景

虽然可以额外写一些功能代码,去处理传入people类的初始化参数

但是在实际项目中,会使代码的可读性、维护性降低(试想在一页几千行的代码中,找到处理的函数…)

也不符合 pythonic,不是么?

结语

工作中 pycharm 总是提示我

Method ‘xxx’ may be ‘static’

今天终于明白了 pycharm 的良苦用心… …