乐趣区

关于python:Python函数式编程系列011类与类型

这篇文章里,咱们试图稍微讨论一下类与类型的概念。当然,内容是很肤浅甚至是更关注实际的。不过这种解读可能更有裨益。

我在后面水管模型的叙述中,始终将函数式的假想敌人设想成「过程式」编程。这里当然咱们就未免地对编程语言做一些简略的分类。许多人认为函数式编程相同的概念是面向对象编程,但其实这里存在了十分多的误会。我在后面的文章里,始终强调如果要应用一个新的概念,就必须至多在那篇文章里做阐明。一路走来,函数式的基本概念曾经帮咱们解决了大部分问题。这个到实现列表为止,都让人感觉能够承受。但前面,为了更不便地获取一些值曾经做一些类型标注,咱们在此必须要引入类的概念了,理由如下。

  1. 之前获取 List 头的函数应用 head(pair) 这种语法,再各种嵌套的时候,会升高很多可读性(尽管咱们实现的 compose/and_then 局部解决了这个问题)。
  2. 比方 pair(a, b) 这个数据,咱们更像稳固地作为一些数据 / 值的构造。这个构造体自身是稳固的。而后单纯用函数的定义会使它们的应用过于涣散。
  3. 在写一个代码的时候 h(g(f(x))) 这种写法往往不和人的思考逻辑,事实上咱们是思考 f 再思考 g 这样的。而类的调用使得咱们能够按失常思维逻辑实现这件事x.f().g().h()
  4. 最初就是对于类型标注的须要,Python的类型标注,大多依赖于相干类的定义。所以有必要引入类的概念。

不过,咱们首先要理清一下 类型 的区别。大部分语言里,类型 在广义上就是指程序语言自带的值的类别; 则是面向对象的概念,和咱们所谓的对象、实例化这些概念无关。而如果你考查大部分类型标注零碎,你能够发现类的新建的时候,就是在发明一个新的 类型 。真正比拟好和简答地了解,就是 类型 示意的是一些值的汇合,它们独特有一部分性质。而 则是比 类型 提供更多的概念,比方「属性」、「办法」、「继承」这些。当然,在 Python 之中,类型 也就天经地义承当了 元类 ,即的概念。

然而, 事实上也有「过程式」和「函数式」的两面。譬如,咱们在应用上面一个学生类的时候,add_age波及到了对本人属性的变动,这就波及到了「变量」或者咱们之前提到的屋子模型的概念。这是咱们想要防止的事。

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

    def add_age(self, add_num):
        self.age += add_num

另一个波及到的问题是恒等性的概念,就是咱们如何判断两个事物是否相等的逻辑。类编程事实上,每一次实例化的后果都是一个不同后果,比方下面的例子中,咱们能够尝试定义上面两个学生,他们尽管名字和年龄一样,然而咱们却得出他们不是一个人的后果。

>>> a = Student("a", 11)
>>> b = Student("a", 11)            
>>> a == b
False

在技术实现上说,Python比拟的是 ab的 hash 值,或者说他们是两个屋子,Python 比拟的是屋子而不是外面的值。咱们打印 ab的 hash 值能够发现他们是不同的。

>>> a.__hash__()
137886633561
>>> b.__hash__()
137886633567

而在函数式例子里,咱们只想把类作为一个数据组合工具,并提供局部继承概念的货色,这就显得没有必要了。一个办法是,咱们对 == 的逻辑改写,比方下面的例子里,咱们要重载 __eq__ 办法:

class Student:
    def __init__(self, name, age):
        self.name = name
        self.age  = age
    
    def add_age(self, add_num):
        self.age += add_num
    
    def __eq__(self, other):
        return (self.name == other.name) and (self.age == other.age)


>>> a = Student("a", 11) 
>>> b = Student("a", 11)
>>> a == b
True

一个更好的办法,就是应用 dataclass 的概念。它的名字自身也就是说咱们在把这个类的对象当做数据来看。在下面的例子里,咱们就能够应用 dataclass 的润饰器就行了,并且咱们甚至能够省略 __init__ 办法:

from dataclasses import dataclass

@dataclass
class Student:
    name: str
    age: int

咱们能够发现,这样执行,咱们失去了咱们之前期待的答案:

>>> a = Student("a", 11) 
>>> b = Student("a", 11)
>>> a == b
True

然而 dataclass 并不限度你产生副作用的函数,比方咱们举例中的 add_age。在函数式编程中,咱们能够应用一个Point Free 的写法,通过返回批改过参数的对象就行了,比方下面的例子能够改写为:

from dataclasses import dataclass

@dataclass
class Student:
    name: str
    age: int

    def add_age(self, add_num):
        return Student(self.name, self.age + add_num)

这种写法有个益处,就是咱们能改成链式的调用。毛病就是咱们可能须要新建一个变量名贮存这个后果:

>>> Student("a", 1).add_age(2).add_age(3).add_age(-1)
Student(name='a', age=5)

这基本上形成了这个系列文章前面的次要格调,除了少部分不可短少的副作用,以及通过隔离它们到一个很小的范畴内。其余局部咱们将十分好地利用对象式编程的构造分层和我的项目代码治理的能力,以及函数式的个性来解决大部分的问题。

退出移动版