当前位置: 首页 > 后端技术 > Python

Python函数式编程系列011:类和类型

时间:2023-03-26 14:13:11 Python

在这篇文章中,我们尝试稍微讨论一下类和类型的概念。当然,内容很浅,甚至更实用。但这种阅读可能更有帮助。在前面对水管模型的描述中,我一直把函数式的假想敌想象成“过程式”编程。这里当然不能不对编程语言做一些简单的分类。很多人认为函数式编程的对立概念是面向对象编程,其实这里有很多误解。在我之前的文章中,我一直强调,如果你想使用一个新的概念,你至少要在那篇文章中解释它。一路走来,函数式风格的基本概念帮助我们解决了大部分问题。在列表实现之前这是可以接受的。但是后来为了更方便的获取一些值,做了一些类型注解,这里必须引入类的概念,原因如下。前面获取列表头的函数使用的是head(pair)语法,嵌套时会降低很多可读性(虽然我们实现的compose/and_then部分解决了这个问题)。比如pair(a,b)的数据更像是一个稳定的数据/值结构。结构本身是稳定的。然后简单地使用函数定义会使它们的使用过于松散。在写代码的时候,h(g(f(x)))的写法往往不符合人的思维逻辑。事实上,我们首先考虑f,然后考虑g。类的调用让我们按照正常的思维逻辑来完成x.f().g().h()这件事情。最后,需要类型注释。Python类型注释主要依赖于相关类的定义。所以有必要引入类的概念。但是,我们首先需要弄清楚类和类型之间的区别。在大多数语言中,狭义的类型指的是编程语言自带的值的范畴;类是一个面向对象的概念,它与我们所说的对象和实例化有关。如果你查看大多数类型注释系统,你会发现当创建一个类时,它正在创建一个新类型。更好更简单的理解是,类型表示共享某些属性的值的集合。类提供了比类型更多的概念,例如“属性”、“方法”和“继承”。当然,在Python中,类型自然而然地采用了元类的概念,即类的类。然而,类实际上同时具有“过程”和“功能”方面。比如我们在使用下面的student类时,add_age涉及到自身属性的变化,这就涉及到“变量”的概念或者我们前面提到的房子模型。这是我们要避免的事情。classStudent:def__init__(self,name,age):self.name=nameself.age=agedefadd_age(self,add_num):self.age+=add_num另一个涉及到的问题是身份的概念,就是逻辑我们如何判断两个事物是否相等。类编程其实每次实例化的结果都是不同的。例如,在上面的例子中,我们可以尝试定义如下两个学生。虽然他们同名同龄,但我们可以断定他们不是同一个人。.>>>a=Student("a",11)>>>b=Student("a",11)>>>a==bFalse从技术上讲,Python比较a和b的哈希值,在其他也就是说,它们是两栋房子,Python比较的是房子,而不是里面的值。我们把a和b的hash值打印出来,发现是不一样的。>>>a.__hash__()137886633561>>>b.__hash__()137886633567在功能示例中,我们只是想将类作为数据组合工具,提供一些继承概念,这是不必要的。一种方法是我们重写==的逻辑。比如上面的例子,我们需要重载__eq__方法:+=add_numdef__eq__(self,other):return(self.name==other.name)和(self.age==other.age)>>>a=Student("a",11)>>>b=Student("a",11)>>>a==bTrue更好的方法是使用数据类的概念。它的名称本身就意味着我们正在将此类的对象视为数据。在上面的例子中,我们可以只使用dataclass装饰器,甚至可以省略__init__方法:fromdataclassesimportdataclass@dataclassclassStudent:name:strage:int我们可以发现,这样执行,我们得到的答案就是我们预期之前:>>>a=Student("a",11)>>>b=Student("a",11)>>>a==bTrue但是数据类不会限制你使用有副作用的函数,比如如我们示例中的add_age。在函数式编程中,我们可以使用PointFree的写法,只需要返回修改参数的对象即可。例如上面的例子可以改写为:fromdataclassesimportdataclass@dataclassclassStudent:name:strage:intdefadd_age(self,add_num):returnStudent(self.name,self.age+add_num)的好处是我们可以将其更改为链式调用。缺点是我们可能需要创建一个新的变量名来存储结果:>>>Student("a",1).add_age(2).add_age(3).add_age(-1)Student(name='a',age=5)这基本上构成了本系列其余部分的主要风格,除了一些不可或缺的副作用,并通过将它们隔离到一个小范围内。在其他部分,我们将很好地利用基于对象编程的结构分层和项目代码管理能力,以及功能特性来解决大部分问题。