您的位置:首页 > 娱乐 > 八卦 > 71、Python之函数式编程:不能定义常量,Python如何支持不可变性?

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

2024/10/6 4:13:49 来源:https://blog.csdn.net/dqrcsc/article/details/142207147  浏览:    关键词: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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com