看到一个观点:用纯函数+纯数据类可以很大程度上代替掉面向对象,不用创建复杂的class类。

比如一个面向对象的写法:clazz.run(args)完全可以替换成run(clazz,args)的写法,让所有的函数都是一等公民,不必依赖于类而存在。

而run方法还可以做为变量去做另外一个函数的入参或者出参,比如:invoke_run(run,data),这样可以极大的增加程序的灵活性,而不必定义可能只会被继承一次的接口类:RunInterface。

这是类方法被代替的案例,那么对象方法怎么被代替呢。对象方法里面经常会依赖了对象参数。这个时候,就可以用到纯数据类了。

比如一个User对象,有login方法,依赖了username和password两个参数。之前用python写面向对象的时候,感觉有点冗余:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class User:
    def __init__(self, username, password):
        self.username = username
        self.password = password

    def login(self, username, password):
        if username == self.username and password == self.password:
            print("登录成功!")
            return True
        else:
            print("用户名或密码错误。")
            return False


# 示例
if __name__ == "__main__":
    user = User("example_user", "example_password")
    user.login("example_user", "example_password")  # 应该打印出 "登录成功!"
    user.login("wrong_user", "example_password")  # 应该打印出 "用户名或密码错误。"

而改成函数式编程写法,可以这样,将login函数和User数据类独立:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from dataclasses import dataclass


@dataclass
class User:
    username: str
    password: str


def login(self: User, username, password):
    if username == self.username and password == self.password:
        print("登录成功!")
        return True
    else:
        print("用户名或密码错误。")
        return False

之前不是有句话嘛,程序=数据结构+算法。那么,在函数式编程里,程序=纯数据类+纯函数。尽量让函数只依赖于它的入参,不依赖于其它的全局变量。如果确实要依赖全局变量呢?那就再加一个函数入参GlobalVariable数据类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@dataclass
class GlobalVariable:
    key1: str
    key2: str
    key3: str


def login(gv: GlobalVariable, self: User, username, password):
    if username == self.username and password == self.password:
        print("登录成功!")
        return True
    else:
        print("用户名或密码错误。")
        return False

def main():
    print(__file__)
    gv = GlobalVariable("value1", "value2", "value3")
    user = User("example_user", "example_password")
    login(gv, user, "example_user", "example_password")  # 应该打印出 "登录成功!"
    login(gv, user, "wrong_user", "example_password")  # 应该打印出 "用户名或密码错误。"


if __name__ == "__main__":
    main()

纯函数的好处就是:极大的提高了可测试性。我们在做单测时,可以想怎么mock就怎么mock,还可以保证多次测试的一致性。

上面的@dataclass的python 3.7以后引用的一个系统库,可以自动添加__init____repr____eq__等,减少了样板代码,使类更加简洁。

简洁的好处就是:代码量越少,出bug的可能性越小。