很多初学者在了解了一门编程语言的基本语法和使用之后,面对一个“开发需求”时仍然会觉得无从下手、没有思路/套路,本小节内容就是为了解决该问题,那到底什么是编程范式呢?
编程范式指的就是编程的套路,打个比方,如果把编程的过程比喻为练习武功,那么编程范式指的就是武林中的各种流派,而在编程的世界中常见的流派有:面向过程、函数式、面向对象等等。
在正式介绍之前,我们需要强调一下:“功夫的流派没有高低之分,只有习武的人才有高低之分”,在编程世界里更是这个样子,各种编程范式在不同的场景下都各有优劣,谁好谁坏不能一概而论。那么接下来我们就来一起认识一下他们!
面向过程核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么,基于面向过程开发程序就好比在设计一条流水线,是一种机械式的思维方式,这正好契合计算机的运行原理:任何程序的执行最终都需要转换成 CPU 的指令流水按过程调度执行,即无论采用什么语言、无论依据何种编程范式设计出的程序,最终的执行都是过程式的。
更加详细的讲,如果程序一开始是要着手解决一个大的问题,按照过程式的思路就是把这个大的问题分解成很多个小问题或子过程去实现,然后依次调用即可,这极大地降低了程序的复杂度。举例如下:
写一个远程备份数据的程序,分三步:本地数据打包,上传至云服务器,检测备份文件可用性
import os
import time
# 1、本地数据打包
def data_backup(folder):
"""
该函数用于备份文件夹
:param folder: 要备份的文件夹
:return: zip包
"""
print(f'找到备份目录:{folder}')
print(f'正在备份···')
zip_file = f'/tmp/backup_{time.strftime("%F")}.zip'
print(f'备份成功,备份文件为:{zip_file}')
return zip_file
# 2、上传至云服务器
def cloud_upload(file):
"""
该函数用户上传文件到服务器
:param file: 要上传的文件
:return: link
"""
print(f'connecting cloud storage center...')
print(f'cloud storage connected')
print(f'upload {file} to cloud...')
link = f'https://www.xxx.com/bak/{os.path.basename(file)}'
print('close connection')
return link
# 3、检测备份文件的可用性
def data_backup_check(link):
"""
该函数用于检测备份文件的可用性
:param link:
:return:
"""
print(f'下载文件:{link},验证文件是否无损···')
zip_file = data_backup('/Users/xxx/Desktop/python')
link = cloud_upload(zip_file)
data_backup_check(link)
将复杂的问题流程化,进而简单化
程序的可扩展性极差,因为一套流水线或者流程就是用来解决一个问题,就好比生产汽水的流水线无法生产汽车一样,即便是能,也得是大改,而且改一个组件,与其相关的组件可能都需要修改,比如我们修改了cloud_upload的逻辑,那么依赖其结果才能正常执行的data_backup_check也需要修改,这就造成了连锁反应,而且这一问题会随着程序规模的增大而变得越发的糟糕。
这就验证了一句话:牵一发而动全身
import os
def cloud_upload(file): # 加上异常处理,在出现异常的情况下,没有link返回
try:
print("connecting cloud storage center...")
print("cloud storage connected")
print("upload [%s] to cloud..." % file)
link = f'https://www.xxx.com/bak/{os.path.basename(file)}'
print('close connection')
return link
except Exception:
print('upload error')
finally:
print('close connection.....')
def data_backup_check(link): # 加上对参数link的判断
if link:
print("下载文件: {link}, 验证文件是否无损...")
else:
print('链接不存在')
面向过程的程序设计一般用于那些功能一旦实现之后就很少需要改变的场景, 如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程去实现是极好的,但如果你要处理的任务是复杂的,且需要不断迭代和维护, 那还是用面向对象最为方便。
函数式编程并不是用函数编程这么简单,而是将计算机的运算视为数学意义上的运算,比起面向过程,函数式更加注重的是执行结果而非执行的过程,代表语言有:Haskell、Erlang。而Python并不是一门函数式编程语言,但是仍为我们提供了很多函数式编程好的特性,如:lambda,map,reduce,filter
对比使用def关键字创建的是有名字的函数,使用lambda关键字创建则是没有名字的函数,即匿名函数
lambda 参数1,参数2,...: expression
# 1、定义
lambda x, y, z : x + y + z
# 等同于
def func(x, y, z):
return x + y + z
# 2、调用
# 方式一
res = (lambda x, y, z : x + y + z)(1, 2, 3)
# 方式二
func = lambda x, y, z : x + y + z # 匿名的本质就是要没有名字,所以此处为匿名函数指定名字是没有意义的
res = func(1, 2, 3)
匿名函数与有名函数有相同的作用域,但是匿名意味着引用计数为0,使用一次就释放,所以匿名函数用于临时使用一次的场景,匿名函数通常与其他函数配
合使用,我们以下述字典为例子来介绍它
salaries = {
'jack': 2000,
'tom': 5000,
'tim': 3000,
'cook': 9000
}
要想取得薪水的最大值和最小值,我们可以使用内置函数max和min(为了方便开发,Python解释器已经为我们定义好了一系列常用的功能,称之为内置的函数,我们只需要拿来使用即可)
salaries = {
'jack': 2000,
'tom': 5000,
'tim': 3000,
'cook': 9000
}
print(max(salaries))
print(min(salaries))
# 结果
tom
cook
内置max和min都支持迭代器协议,工作原理都是迭代字典,取得的是字典的键,因而比较的是键的最大和最小值,而我们想要的是比较值的最大值与最小值,于是做出如下改动
# 函数max会迭代字典salaries,每取出一个“人名”就会当作参数传给指定的匿名函数,然后将匿名函数的返回值当作比较依据,最终返回薪资最高的那个人的名字
salaries = {
'jack': 2000,
'tom': 5000,
'tim': 3000,
'cook': 9000
}
print(max(salaries, key=lambda item: salaries[item]))
print(min(salaries, key=lambda item: salaries[item]))
# 结果
cook
jack
同理,我们直接对字典进行排序,默认也是按照字典的键去排序的
>>> sorted(salaries)
['cook', 'jack', 'tim', 'tom']
函数map、reduce、filter都支持迭代器协议,用来处理可迭代对象,我们以一个可迭代对象array为例来介绍它们三个的用法
array = [1, 2, 3, 4, 5]
需求:对array的每个元素做平方处理,可以使用map函数
array = [1, 2, 3, 4, 5]
# 解析:map会依次迭代array,得到的值依次传给匿名函数(也可以是有名函数),而map函数得到的结果仍然是迭代器。
res = map(lambda item: item ** 2, array)
print(res)
print(list(res))
# 结果
<map object at 0x0000013F78A42560>
[1, 4, 9, 16, 25]
需求:对array进行过滤操作,这就用到了filter函数,比如过滤出大于3的元素
array = [1, 2, 3, 4, 5]
# 解析:filter函数会依次迭代array,得到的值依次传给匿名函数,如果匿名函数的返回值为真,则过滤出该元素,而filter函数得到的结果仍然是迭代器。
res = filter(lambda item: item > 3, array)
print(res)
print(list(res))
# 结果
[4, 5]
需求:对array进行合并操作,比如:求和运算,这就用到了reduce函数
reduce函数可以接收三个参数,一个是函数,第二个是可迭代对象,第三个是初始值
# reduce在Py2中是内置函数,在Py3中则被集成到模块 functools 中,需要导入才能使用。
from functools import reduce
array = [1, 2, 3, 4, 5]
# 解析:
# 没有初始值,reduce函数会先迭代一次array得到的值作为初始值,作为第一个值数传给x,然后继续迭代一次array得到的值作为第二个值传给y,运算的结果为3。
# 将上一次reduce运算的结果作为第一个值传给x,然后迭代一次array得到的结果作为第二个值传给y,依次类推,知道迭代完array的所有元素,得到最终的结果15。
res = reduce(lambda x, y: x + y, array)
print(res)
# 也可以为reduce指定初始值
res2=reduce(lambda x, y: x + y, array, 100)
print(res2)
# 结果
15
115
我们介绍map、filter、reduce只是为了带大家了解函数式编程的大致思想,在实际开发中,我们完全可以用列表生成式或者生成器表达式来实现三者的功能。