当前位置: 首页 > news >正文

71、Python之函数式编程:不能定义常量,Python如何支持不可变性?

引言

其他编程语言中可以使用const或者final等关键字来定义不可修改的变量,也就是常量。但是,Python中似乎没有提供类似的关键字。所以,时不时总会有同学提出这样的质疑:都不能定义常量,Python对函数式编程的支持也太弱了吧。其实,如果对函数式编程的不可变性有更加深入的理解,就不会有这样的疑问了。

本文打算从软件架构作为展开,逐步对上面的疑问进行解答。

本文的主要内容有:

1、简单聊下软件架构

2、三种主流范式对架构的支持

3、Python关于不可变性的设计哲学

1、简单聊下软件架构

由于生产、生活中的不确定性越来越大,软件系统所需要支持的业务需求也变得更加不确定,所以,一个真正满足业务需求的软件系统,大部分工作都是在对系统进行修改、扩展、维护。我们曾经提过的各种设计原则,比如开闭原则(OCP)、单一职能原则(SRP)等,其实都是为了让软件系统在设计之初,就考虑了后续能够更加容易扩展和维护。

所谓的“软件架构”就是根据业务需求,综合应用各种设计规则,进行软件系统的整体设计,从而可以用最小的人力成本来满足构建和维护系统的需求。

所以,衡量一个软件架构的优劣,只要看它满足业务需求所需要的成本即可。如果成本比较低,并且在整个软件系统的生命周期内一直都能维持这样的低成本,那么这个系统的架构就是优良的。反之,如果该系统的每次发布都会提升下一次变更的成本,那么这个架构就是不好的。

如果进一步深挖,软件系统的架构设计是为了保证易于扩展和维护,落脚到程序代码的组织上,就要保证代码的可读性、易于调试、测试。

要保证代码的可读性和易于调试、测试,就要尽量规避编码的随意性,一般会从这些方面考虑:

1)程序代码格式清晰、有必要的注释。

2)程序代码的控制逻辑相对清晰,控制逻辑的转移更加明确。

3)程序代码的状态、结果更加具有确定性。

2、三种主流范式对软件架构的支持

如果从软件架构的质量,以及代码的可读性和易于调试、测试的角度,同时结合编程范式与编程语言之间相互独立的关系,来重新看编程范式的话,所谓的“编程范式”,似乎是计算机科学领域的大佬和先贤,关于如何开发更加易于扩展和维护的软件系统,所总结出的“苦口婆心”的真知灼见。

层出不穷的编程语言新特性,以及代码组织编写的自由度,给程序员进行创造性的发挥,带来了极大的便利的同时,也在代码编写上引入了更大的随意性。

编程范式,从表面看来,似乎是关于”应该怎么做“的建议。但是,如果进行更加本质的思考,编程范式的核心似乎是关于”最好不要做什么“的规劝。

所以,编程范式对软件架构的支持,其实是一种”做减法“的支持:

1)过程式编程范式,其实是对程序员不要随意使用goto进行无限制的直接控制转移的规劝,是对控制逻辑清晰度的保证。

2)面向对象编程范式,本质上是对函数指针随意使用的限制,也就是对程序控制逻辑进行隐性的间接转移的限制,也是对控制逻辑清晰度的保证。

3)函数式编程范式,最核心的特性不可变性,本质上是对赋值操作加上了限制,是对程序的状态和结果的确定性的保证。

范式都是建议性的,你可以做,但是我强烈建议你不要做。当然,如果一门编程语言对某种编程范式是完全支持的,那么这种编程范式就变成了强制性的。所以,还是要看具体的编程语言的设计哲学以及最终实现。

3、Python关于不可变性的设计哲学

Python中确实没有内置的常量定义机制,这与Python的设计哲学有关。Python更加倾向于使用约定而不是强制性规则来指导编程实践。

当然,尽管没有内置常量,在Python中,我们仍然有其他方式来支持函数式编程的不可变性。

1)通过约定使用常量

我们可以使用全大写字母命名变量,从而表示它们应该被视为常量,这是Python社区的约定。

aea6db49289fdd3cbdc7549749185e50.jpeg
2)使用不可变的数据结构

函数式编程强调使用不可变的数据结构,Python中的元组、字符串以及不可变集合(frozenset)等,都是不可变的(虽然其存储的元素对象的可变性决定了这种不可变性的彻底性)。

3)使用dataclass中的frozen

from dataclasses import dataclass@dataclass(frozen=True)
class Point:x: inty: intif __name__ == '__main__':p1 = Point(10, 20)print(p1)print(p1.x)# 尝试进行重新赋值,会报错p1.x = 100

执行结果:

e3c5bb0991cc0834ff44aa7b2666cec2.jpeg

4)自定义不可变类型

类似于dataclass的frozen=True,本质上都是通过对__setattr__()方法的覆盖,来禁止修改实例属性,从而实现了对不可变性的支持。

所以,我们也是可以通过这种机制来实现不可变性的。

直接看代码:

class Point:def __init__(self, x, y):object.__setattr__(self, 'x', x)object.__setattr__(self, 'y', y)def __setattr__(self, key, value):raise AttributeError(f'属性{key}不允许修改')if __name__ == '__main__':p1 = Point(10, 20)print(p1)print(p1.x)# 尝试进行重新赋值,会报错p1.x = 100

执行结果:

eb05e591e10b12d5c03c8a270b084553.jpeg

尽管Python对函数式编程的不可变性有足够的支持,我们还是需要对函数式范式关于不可变性要求的初衷,有更加深入的理解,避免机械性地执行,而使得编程行为变得僵化。

在Python等面向对象的语言中,不可变数据结构似乎有些另类,但当我们从函数式编程的角度思考时,就会发现状态变化是许多令人困惑的问题的根源。使用不可变数据结构有助于我们拨云见日,直抵问题的核心。

所以,不可变性其实是对状态和结果更加具有确定性的一种追求,只要心里始终有这个清晰的目标,并向着这个目标努力,对于不可变性,就已经有了足够深入的理解。

总结

本文从软件架构作为切入点,将程序代码的可读性、易于调试、测试的建议,和编程范式的各种减法型规范,联系在一起,从而对设计原则、软件架构及编程范式有了更深入的理解。最后,通过Python关于不可变性的支持,探讨了关于程序状态和结果的确定性的本质。

以上,就是本文的全部内容了,感谢您的拨冗阅读,希望对您有所帮助。

fad934a3f052ad86fc226942f4cefba6.jpeg


http://www.mrgr.cn/news/29140.html

相关文章:

  • 每日学习一个数据结构-FST数据结构与算法
  • rust快速创建Tauri App ——基于create-tauri-app
  • 变电站缺陷数据集8307张,带xml标注和txt标注,可以直接用于yolo训练
  • 《珠江水运》
  • C++ 类的默认成员函数-析构函数
  • C++使用Socket编程实现一个简单的HTTP服务器
  • NISP 一级 | 6.2 移动智能终端安全威胁
  • AG32 MCU与内置FPGA的FLASH空间如何划分
  • 一款免费开源且功能强大的思维导图软件-思绪思维导图
  • docker安装部署时的资源文件路径问题以及使用pecl工具简洁方便地安装php扩展
  • 如何在自动化测试中应用装饰器、多线程优化自动化架构?
  • Python | Leetcode Python题解之第414题第三大的数
  • 精选6大高效通信与链接API助力程式开发
  • C语言 | Leetcode C语言题解之第414题第三大的数
  • 【C++语言】C/C++内存管理
  • Java ETL - Apache Beam 简介
  • 绝缘子缺陷检测数据集
  • frp内网穿透功能使用教程
  • 【H2O2|全栈】关于CSS(5)如何制作一个搜索网页的首页?
  • 【RabbitMQ】可靠性传输