Python之路:(七)函数式编程
函数式编程
一、背景
在学习函数之前,一直遵循:面向过程编程,即:根据业务逻辑从上到下实现功能,其往往用一长段代码来实现指定功能,开发过程中最常见的操作就是粘贴复制,也就是将之前实现的代码块复制到现需功能处,如下:
while True: if cpu利用率 > 90%: #发送邮件提醒 连接邮箱服务器 发送邮件 关闭连接 if 硬盘使用空间 > 90%: #发送邮件提醒 连接邮箱服务器 发送邮件 关闭连接 if 内存占用 > 80%: #发送邮件提醒 连接邮箱服务器 发送邮件 关闭连接
定眼一看上述代码,if条件语句下的内容可以被提取出来公用,如下:
def 发送邮件(内容) #发送邮件提醒 连接邮箱服务器 发送邮件 关闭连接 while True: if cpu利用率 > 90%: 发送邮件('CPU报警') if 硬盘使用空间 > 90%: 发送邮件('硬盘报警') if 内存占用 > 80%:
对于上述的两种实现方式,第二次必然比第一次的重用性和可读性要好,其实这就是函数式编程和面向过程编程的区别:
- 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可
- 面向对象:对函数进行分类和封装,让开发“更快更好更强...”
二、函数的定义
def 函数名(参数): ... 函数体 ...
函数的定义主要有如下要点:
- def:表示函数的关键字
- 函数名:函数的名称,日后根据函数名调用函数
- 函数体:函数中进行一系列的逻辑计算,如:发送邮件、计算出 [11,22,38,888,2]中的最大数等...
- 参数:为函数体提供数据
- 返回值:当函数执行完毕后,可以给调用者返回数据。
以上要点中,比较重要有参数和返回值:
1、返回值
函数是一个功能块,该功能到底执行成功与否,需要通过返回值来告知调用者。
def 发送短信(): 发送短信的代码... if 发送成功: return True else: return False while True: # 每次执行发送短信函数,都会将返回值自动赋值给result # 之后,可以根据result来写日志,或重发等操作 result = 发送短信() if result == False: 记录日志,短信发送失败...
2、参数
为什么要有参数?
不使用参数:
def CPU报警邮件() #发送邮件提醒 连接邮箱服务器 发送邮件 关闭连接 def 硬盘报警邮件() #发送邮件提醒 连接邮箱服务器 发送邮件 关闭连接 def 内存报警邮件() #发送邮件提醒 连接邮箱服务器 发送邮件 关闭连接 while True: if cpu利用率 > 90%: CPU报警邮件() if 硬盘使用空间 > 90%: 硬盘报警邮件() if 内存占用 > 80%: 内存报警邮件()
使用参数:
def 发送邮件(邮件内容) #发送邮件提醒 连接邮箱服务器 发送邮件 关闭连接 while True: if cpu利用率 > 90%: 发送邮件("CPU报警了。") if 硬盘使用空间 > 90%: 发送邮件("硬盘报警了。") if 内存占用 > 80%: 发送邮件("内存报警了。")
函数的参数分为以下几种:
- 普通参数
- 默认参数
- 指定参数
- 非关键字可变长参数
- 关键字可变长参数
- 万能参数
普通参数传递:
# ######### 定义函数 ######### # name 叫做函数send的形式参数,简称:形参 def send(name): print(name) # ######### 调用函数 ######### # "lianglian" 叫做函数send的实际参数,简称:实参 send("lianglian")
lianglian
指定参数传递:
# 指定参数传递 def send(A,B,C): print(A,B,C) send(B="panwenbin", A="lianglian", C="zhaohongfei") # 指定参数必须所有参数都要指定
lianglian panwenbin zhaohongfei
默认参数:
# 默认参数,没有传递参数,使用默认参数,传递了则使用传递的参数 def send(A, B, C="SB"): print(A, B, C) send("eric", "alex")
eric alex SB
关键字可变长参数:
# 关键字可变长参数,也可接收任意个参数,参数传递形式:key-value def send(**kwargs): print(kwargs) f1_dict = { 'a': 'aaa', 'b': 'bbb', 'c': 'ccc' } send(a='aaa', b='bbb', c='ccc') send(**f1_dict)
{'a': 'aaa', 'c': 'ccc', 'b': 'bbb'}
{'a': 'aaa', 'b': 'bbb', 'c': 'ccc'}
非关键字可变长参数:
# 非关键字可变长参数,可接收任意个参数 def send(*args): print(args) send("panwenbin", "lianglian", "zhaohongfei") send(*["panwenbin", "lianglian", "zhaohongfei"]) # 使用"*"可遍历序列,递归传參
('pawenbin', 'lianglian', 'zhaohongfei')
('panwenbin', 'lianglian', 'zhaohongfei')
# 万能参数,可接收任意个参数,关键字可变长参数和非关键字可变长参数同时使用,接收任意类型任意个数参数 def send(*args, **kwargs): print(*args) print(*kwargs) send(1, 11, 21, k1='k1', k2='k2')
1 11 21
k2 k1
python中使用到万能参数的内置函数:
# string的format()方法用到万能参数的例子 s1 = "i am {0}, age {1}".format("lianglian", 12) print(s1) s2 = "i am {0}, age {1}".format(*["lianglian", 12]) print(s2) s3 = "i am {name}, age {age}".format(name="lianglian", age=12) print(s3) dict={'name': 'liangian', 'age':12} s4 = "i am {name}, age {age}".format(**dict) print(s4)
i am lianglian, age 12
i am lianglian, age 12
i am lianglian, age 12
i am liangian, age 12
====注意事项====
# python 函数多次定义被覆盖 def f1(a1, a2): return a1 + a2 def f1(a1, a2): return a1 * a2 ret = f1(8,8) print(ret)
64
python 参数传递是引用:
# python 参数传递是引用 def f1(a1): a1.append(999) li = [11, 22, 33, 44] f1(li) print(li)
[11, 22, 33, 44, 999]
函数内定义的变量只在该函数内有效(注意作用域)
# 函数内定义的变量只在该函数内有效(注意作用域) # global 修改全局变量 NAME = 'alex' def f1(): age = 18 global NAME # 使用global修改过全局生效,作用于全局 NAME = 'lianglian' print(age, NAME) def f2(): age = 19 print(age, NAME) f1() f2()
18 lianglian
19 lianglian
序列(list,tuple...) 行首定义的序列(全局)可以修改 但是不能重新赋值:
# 序列(list,tuple...) 行首定义的序列(全局)可以修改 但是不能重新赋值 group = [11,22,33,44] def f1(): age = 18 group.append(55) print(age, group) def f2(): age = 19 print(age, group) f1() f2()
18 [11, 22, 33, 44, 55]
19 [11, 22, 33, 44, 55]
函数实參和形參之间是引用:
# 函数实參和形參之间是引用 lists = [1, 2, 3, "q"] print(id(lists)) # 创建变量这个变量得时候的地址空间 def name(li): print(id(li)) # 引用实际参数,在这里内存地址空间还以一样 li = "c" print(li) # 只在这个函数内生效,就像是在这个函数内重新定义了一个"li"变量,和外面那个没关系 print(id(li)) # 这里是重新赋值了形参,所以地址空间变了 name(lists) print(lists)
4494019272
4494019272
c
4492796232
[1, 2, 3, 'q']
函数内重新赋值作用域只限于函数内:
# 函数内重新赋值作用域只限于函数内,函数内变了,外面没变 name = "wu" def show(): name='alex' print(name) show() print(name)
alex
wu
lambda表达式
# lambda 表达式会自动retrun结果 # lambda表达式 (简单的函数表达式,只能写一行) def f1(a1): return a1 + 100 ret = f1(10) print(ret) f2 = lambda a1: a1 + 100 r2 = f2(10) print(r2)
装饰器
装饰器也是个函数,只不过该函数可以具有特殊的含义,装饰器用来装饰函数或类,使用装饰器可以在函数执行前和执行后添加相应操作(装饰:在不改变原函数本身情况下,给他添加额外得功能,就像在一个普通士兵给她装备各种牛逼装备只有他能变得异常强大,而装饰器就是哪些装在它身上得牛逼装备)。
一、初创公司有N个业务部门,1个基础平台部门,基础平台负责提供底层的功能,如:数据库操作、redis调用、监控API等功能。业务部门使用基础功能时,只需调用基础平台提供的功能即可。如下:
############### 基础平台提供的功能如下 ############### def f1(): print 'f1' def f2(): print 'f2' def f3(): print 'f3' def f4(): print 'f4' ############### 业务部门A 调用基础平台提供的功能 ############### f1() f2() f3() f4() ############### 业务部门B 调用基础平台提供的功能 ############### f1() f2() f3() f4()
目前公司有条不紊的进行着,但是,以前基础平台的开发人员在写代码时候没有关注验证相关的问题,即:基础平台的提供的功能可以被任何人使用。现在需要对基础平台的所有功能进行重构,为平台提供的所有功能添加验证机制,即:执行功能前,先进行验证。
老大把工作交给 Low B,他是这么做的:
跟每个业务部门交涉,每个业务部门自己写代码,调用基础平台的功能之前先验证。诶,这样一来基础平台就不需要做任何修改了。
当天Low B 被开除了...
老大把工作交给 Low BB,他是这么做的:
只对基础平台的代码进行重构,让N业务部门无需做任何修改
############### 基础平台提供的功能如下 ############### def f1(): # 验证1 # 验证2 # 验证3 print 'f1' def f2(): # 验证1 # 验证2 # 验证3 print 'f2' def f3(): # 验证1 # 验证2 # 验证3 print 'f3' def f4(): # 验证1 # 验证2 # 验证3 print 'f4' ############### 业务部门不变 ############### ### 业务部门A 调用基础平台提供的功能### f1() f2() f3() f4() ### 业务部门B 调用基础平台提供的功能 ### f1() f2() f3() f4()
过了一周 Low BB 被开除了...
老大把工作交给 Low BBB,他是这么做的:
只对基础平台的代码进行重构,其他业务部门无需做任何修改
############### 基础平台提供的功能如下 ############### def check_login(): # 验证1 # 验证2 # 验证3 pass def f1(): check_login() print 'f1' def f2(): check_login() print 'f2' def f3(): check_login() print 'f3' def f4(): check_login() print 'f4'
老大看了下Low BBB 的实现,嘴角漏出了一丝的欣慰的笑,语重心长的跟Low BBB聊了个天:
老大说:
写代码要遵循开发封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
- 封闭:已实现的功能代码块
- 开放:对扩展开发
如果将开放封闭原则应用在上述需求中,那么就不允许在函数 f1 、f2、f3、f4的内部进行修改代码,老板就给了Low BBB一个实现方案:
def w1(func): def inner(): # 验证1 # 验证2 # 验证3 return func() return inner @w1 def f1(): print 'f1' @w1 def f2(): print 'f2' @w1 def f3(): print 'f3' @w1 def f4(): print 'f4'
对于上述代码,也是仅仅对基础平台的代码进行修改,就可以实现在其他人调用函数 f1 f2 f3 f4 之前都进行【验证】操作,并且其他业务部门无需做任何操作。
Low BBB心惊胆战的问了下,这段代码的内部执行原理是什么呢?
单独以f1为例:
# @ + 函数名 # 功能: 1.自动执行这个函数,并且将下面的函数名f1当做参数传递 # 2.将outer函数的返回值,重新赋值给放f1 def outer(func): def inner(): print('log') return func() return inner @outer def f1(): print("F1") f1()
log
F1
当执行的时候,python是由上到下执行的,首先执行到def outer(func):这里把def outer(func)加载到内存
当执行到@outer的时候@outer是python的魔法!它会把它下面的函数进行封装。
把f1这个函数作为def outer(func)的参数传进去!就是:f1()=outer(f1)
然后def outer(func): == outer(f1)就会执行:
def inner(): print('log') return func() # func() == 原函数f1 return inner # 然后把封装后的函数return的结果给原函数 (= f1)
@outer就相当于做了一个重新打了个包,包含原数据和新增数据,然后变成新的包给"f1"
def f1() <==> def inner()
@outer
def f1(): # == def inner():
print("F1") # print('log')
# func()
被装饰的函数如果有参数呢?
# 装饰函数使用参数 def outer(func): def inner(name): # func对应f1,f1有参数,这里也要接收参数好传递给内部funce print("before") r = func(name) print("after") return r # 不要丢弃原函数返回值 return inner @outer def f1(arge): print(arge) return "F1" ret = f1('test') print(ret)
before
test
after
F1
装饰器装饰动态参数函数:
# 装饰器装饰动态参数函数 def outer(func): def inner(*arge, **kwargs): # 装饰器使用动态参数,来应对装饰不同得函数参数不确定情况 print("before") r = func(*arge, **kwargs) # 将原原函数的参数传递进来 print("after") return r # 不要丢弃原函数返回值 return inner @outer def f1(a1): # 一个参数 print(a1) return "F1" @outer def f2(a1, b1): # 两个参数 print(a1, b1) return "F2" print(f1('test1')) print(f2('test1', 'test2'))
before
test1
after
F1
before
test1 test2
after
F2
多装饰器的使用:
USER_INFO = {'is_login': None, 'user_type': 1}
# USER_INFO['is_login']
# USER_INFO.get('is_login', None)
def check_login(func):
def inner(*args, **kwargs):
if USER_INFO.get('is_login', None):
ret = func(*args, **kwargs)
return ret
else:
print("请登录!")
return inner
def check_admin(func):
def inner(*args, **kwargs):
if USER_INFO.get('user_type', None) == 2:
ret = func(*args, **kwargs)
return ret
else:
print('无权查看')
return inner
@check_login
@check_admin
def index():
"""
管理员的功能
:return:
"""
print('Index')
------------------------华丽的分割线-----------------------------
# 装饰器装饰也是有顺序的,代码是从上到下加载到内存中,
# 我们知道装饰器会把被 [@装饰器] 下面的函数装饰,并将它当作参数传递到装饰器里面.
# 装饰顺序结构(0),(1),(2) (0)代表被装饰函数本体
@check_login
@check_admin
def index():
"""
管理员的功能
:return:
"""
print('Index')
if USER_INFO.get('is_login', None): # (2) check_login函数体
if USER_INFO.get('user_type', None) == 2: # (1) check_admin函数体
ret = print('Index') # (0) index函数本体
return ret # (0) index函数返回值
else:
print('无权查看') # (1) check_admin函数体
else:
print("请登录!") # (2) check_login函数体
因此由上可看出,多个装饰器装饰一个函数,其实就是将这个函数层层嵌套到装饰器得函数体里面.
多装饰器的顺序:
代码由上到下执行,加载到内存中,所有当一个函数上面有多个装饰器的时候,从上往下的顺序依次装饰函数,被装饰的函数则从被装饰的函数由下往上层层嵌套到装饰器中。
1、最上面的装饰器是最外一层嵌套
2、嵌套需要注意的是,最外层包裹内层,执行时是从上往下,考虑嵌套后代码执行顺序。
迭代器和生成器
1、迭代器
迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退。另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件
特点:
- 访问者不需要关心迭代器内部的结构,仅需通过next()方法不断去取下一个内容
- 不能随机访问集合中的某个值 ,只能从头到尾依次访问
- 访问到一半时不能往回退
- 便于循环比较大的数据集合,节省内存
>>> a = iter([1,2,3,4,5]) >>> a <list_iterator object at 0x101402630> >>> a.__next__() 1 >>> a.__next__() 2 >>> a.__next__() 3 >>> a.__next__() 4 >>> a.__next__() 5 >>> a.__next__() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
2、生成器
一个函数调用时返回一个迭代器,那这个函数就叫做生成器(generator);如果函数中包含yield语法,那这个函数就会变成生成器;
def func():
yield 1
yield 2
yield 3
yield 4
上述代码中:func是函数称为生成器,当执行此函数func()时会得到一个迭代器。
>>> temp = func() >>> temp.__next__() 1 >>> temp.__next__() 2 >>> temp.__next__() 3 >>> temp.__next__() 4 >>> temp.__next__() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
3、实例
a、利用生成器自定义range
def myrange(arg): start = 0 while True: if start > arg: return yield start start += 1 ret = myrange(3) r1 = ret.__next__() print(r1) r2 = ret.__next__() print(r2) r3 = ret.__next__() print(r3) r4 = ret.__next__() print(r4)
b、利用迭代器访问range
ret = myrange(3) for item in ret: print(item)
递归
在函数体内重复调用自己本身,做到反复运行一个算法达到最终目的然后返回。
def func(n): n += 1 if n >= 4: print(n) return "end" return func(n) r = func(1) # 我传递进去个1,经过递归最后给我返回4 print(r)
# 1*2*3*4*5*6*7 def func(num): if num == 1: return 1 return num * func(num-1) x = func(7) print(x)
其他
1、反射:
利用字符串的形式去对象中(模块)中操作(查找/检测/添加/删除)成员
- getattr
- delattr
- setattr
- hasattr
# 反射:利用字符串的形式去对象中(模块)中操作(查找/检测/添加/删除)成员
# import commons
# __import__("lib.account") # 接受字符串得形式导入模块
def run():
# account/login
inp = input("请输入访问页面:")
m, f = inp.split('/')
obj = __import__('lib.' + m, fromlist=True)
if hasattr(obj, f): # 判断这个模块中是否有这个函数
func = getattr(obj, f) # 得到这个对象
func()
else:
print("404")
if __name__ == '__main__':
run()
2、python文件中特殊方法
vars() # 全部变量
__builtins__ # 内置所有函数
__file__ # 当前py文件的路径,有坑,在当前目录下只能得到文件名
__package__ # 当前在哪个包里
__cached__ # pyc文件路径
__doc__ # 这个文件的注视,顶行"""注释内容
__name__ # 只有执行当前文件时候,当前文件的特殊变量__name__ == mian

浙公网安备 33010602011771号