python:函数——下
函数(下)
12、高阶函数
- 定义:变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
# 高阶函数的特点(必须至少满足一个):
# 1. 接受一个或多个函数作为参数
# 2. 将函数作为返回值返回
def fun():
def fun1():
pass
return fun1
- 我们先来看一个普通的函数
这个函数是对列表list1进行筛选,找到是偶数的元素
list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
def fun(list1):
list2 = []
for i in list1:
if i % 2 == 0:
list2.append(i)
return list2
print(fun(list1))
运行结果 》》》[2, 4, 6, 8, 10]
- 接下来对普通的函数进行更改
list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
def fun(list1):
def fun1(i):
if i % 2 == 0:
return True
list2 = []
for i in list1:
if fun1(i):
list2.append(i)
return list2
print(fun(list1))
运行结果 》》》[2, 4, 6, 8, 10]
我们这里将判断条件拿出来在做一个函数fun1,然后再fun函数中调用fun1函数,虽然他看似已经是高阶函数,但他还是不满足高阶函数的特点,
因为他并没有将函数作为返回值返回,也没有接受一个或多个函数作为参数
,所以我们还要对其再一步更改。
- 更改为高阶函数
list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
def fun1(i):
if i % 2 == 0:
return True
def fun(fun, list1):
list2 = []
for i in list1:
if fun(i):
list2.append(i)
return list2
print(fun(fun1, list1))
运行结果 》》》[2, 4, 6, 8, 10]
这样更改,就满足了
接受一个或多个函数作为参数
这个特点,这样我们就完成了我们的高阶函数。
13、匿名函数
匿名函数,就跟他的字面意思一样,我们不对函数进行命名,因为我们在开发的时候是协同开发,如果每个人写一个函数,都对其进行一个命名的话,会非常占用命名空间,对同事也不是特别友好,因为不能取相同的函数名,不然函数将会被覆盖,所以必须将一些非常简单效果的函数写成匿名函数
- 匿名函数
lambda函数
就是专门用来创建一些简单的函数
filter类
-
filter() 需要传递两个参数 按照你设定的规则,过滤出你想要的数据
1、 传递一个函数
2 、传递一个需要过滤的序列(可迭代的) -
filter类的使用:依旧是对数据筛选的更改
# 这还是高阶函数喔
list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
def fun1(i):
if i % 2 == 0:
return True
print(list(filter(fun1, list1)))
运行结果 》》》[2, 4, 6, 8, 10]
lambda函数
- 下面是一些lambda函数示例:
函数示例 | 说明 |
---|---|
lambda x, y: x*y | 函数输入是x和y,输出是它们的积x*y |
lambda:None | 函数没有输入参数,输出是None |
lambda *args: sum(args) | 输入是任意个数的参数,输出是它们的和(隐性要求是输入参数必须能够进行加法运算) |
lambda **kwargs: 1 | 输入是任意键值对参数,输出是1 |
- 实际操作(对任意2个数进行添加并输出的操作):
# 可以直接这样写
print((lambda a, b: a + b)(10, 20))
运行结果 》》》30
# 另一种写法
r = lambda a, b: a + b
print(r(10, 20))
运行结果 》》》30
filter类 与 lambda函数结合使用写高阶函数
还是前面数据筛选的需求,这一次我们将fun1()用lambda函数代替,再加上filter类的使用,既简化了我们的代码,效果也非常好,还没有函数重名的现象。
list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = lambda i: i % 2 == 0
print(list(filter(result, list1)))
运行结果 》》》[2, 4, 6, 8, 10]
我这里对lambda函数写的比较浅,为大家找到了一遍比较好的lambda函数的博客,感兴趣的小伙伴可以看一下喔!!博客链接
14、闭包
闭包代码原理是c语言编写,咱们不用必须弄懂他,当然,我还是找了一篇博客,感兴趣的朋友可以看看,博客链接
- 闭包是将函数作为返回值也是高阶函数我们也称为闭包,也就是说闭包必须有函数嵌套
- 闭包的好处
通过闭包可以创建一些只有当前函数能访问的变量
可以将一些私有数据藏到闭包中 - 行成闭包的条件
函数嵌套
将内部函数作为返回值返回
内部函数必须要使用到外部函数的变量
在讲闭包前必须要了解:
函数在运行结束后变量会被内存清楚掉,也就是说变量会被销毁掉
,不是很明白的话可以看一下——python的垃圾回收机制,正因如此,我们想要将数据保存起来,不被销毁,我们的闭包的第一个特性就能派上用场
- 闭包的第一个特性:变量不被销毁
def func_out(num1):
def func_inner(num2): # 函数嵌套
result = num1 + num2 # 外层参数调用
print(result)
return func_inner # 将内部函数作为返回值返回
f = func_out(1) # 给num1传一个1
f(2) # 函数调用,给num2传一个2
运行结果 》》》3
f(3)
运行结果 》》》4
这里我们就可以看到闭包的这一特性,在f(2)运行后,并不会将num1的值销毁,所以在f(3)执行的时候,依旧可以调用num1的值
- 闭包的第二个特性:变量不可被更改
def func_out(num1):
def func_inner(num2): # 函数嵌套
num1 = 10
result = num1 + num2 # 外层参数调用
print(result)
print(num1) #这里就可以很清晰的看到num1的变化
func_inner(2)
print(num1)
return func_inner # 将内部函数作为返回值返回
func_out(1) # 函数对象是func_out 函数调用是func_out()
运行结果 》》》
1
12
1
首先我们这样更改,在内层函数中加上了
num1=10
,我们等于没有调用外层函数的参数num1,这个外层函数的num1行参
与内层函数的变量num1
压根就不是一个东西,所以这已经不满足闭包的条件:内部函数必须要使用到外部函数的变量
,所以这个函数连闭包都不是。
- 闭包中让外部变量可以修改
我们先要了解一个相对的概念,如上述的num1,在整个py文件中,他是局部变量,但相对于内部函数,他就是全局变量,这里我们说的让外部变量修改就是对num1进行更改。
这里我们用到了nonlocal,也就是非本地的意思,将num1变量改成外部的参数,这样我们又形成了一个闭包,也让外部变量num1变得可有修改。
def func_out(num1):
def func_inner(num2): # 函数嵌套
# 告诉解释器,此处使用的是外部变量num1
nonlocal num1
# 这里的本意是要修改外部变量num1的值,实际上是重新进行了赋值
num1 = 10
result = num1 + num2 # 外层参数调用
print(result)
print(num1)
func_inner(2)
print(num1)
return func_inner # 将内部函数作为返回值返回
func_out(1) # 函数对象是func_out 函数调用是func_out()
运行结果 》》》
1
12
10
15、装饰器的引入
- 为什么要引入装饰器?
我们可以直接通过修改函数中的代码来完成需求,但是会产生以下一些问题
- 如果修改的函数多,修改起来会比较麻烦
- 不方便后期的维护
- 这样做会违反开闭原则(ocp)
程序的设计,要求开发对程序的扩展,要关闭对程序的修改
- 我们这里有一个简单的函数
def add(a, b):
return a + b
- 如果我们直接,修改,如下,就会违反开闭原则
# 不要这样使用
def fun1():
print('函数开始执行')
print('我是fun1函数')
print('函数执行完毕')
- 正确使用装饰
装饰fun1()函数
def fun1():
print('我是fun1函数')
def fun():
print('函数开始执行')
fun1()
print('函数执行完毕')
fun()
运行结果 》》》
函数开始执行
我是fun1函数
函数执行完毕
装饰add()函数
def add(a, b):
return a + b
def fun(a, b):
print('函数开始执行')
result = add(a, b)
print(result)
print('函数执行完毕')
fun(1, 2)
运行结果 》》》
函数开始执行
3
函数执行完毕
- 这样使用装饰器虽然也可以,但是我们发现每次装饰都要改很多东西,所以我们讲一下通用装饰器
def fun1():
print('我是fun1函数')
def add(a, b):
return a + b
def fun(fn, *args, **kwargs):
print('函数开始执行')
r = fn(*args, **kwargs)
print(r)
print('函数执行完毕')
fun(add, 1, 2)
运行结果 》》》
函数开始执行
3
函数执行完毕
fun(fun1)
运行结果 》》》
函数开始执行
我是fun1函数
None
函数执行完毕
我们将fun函数定义几个行参,第一次是fn,可以传不同的函数,后面就是不定长参数。之后在调用的时候,实参写上我们想要传递的参数就可以辽!!!
16、装饰器的使用
- 前面的装饰运用只是简单介绍一下概念使用,但是他还
不是正式的装饰器,因为正式的装饰器是一个闭包
- 通过装饰器,可以在不修改原来函数的情况下来对函数进行扩展
- 在开发中,我们都是通过装饰器来扩展函数的功能的
闭包条件:
函数嵌套
将内部函数作为返回值返回
内部函数必须要使用到外部函数的变量
- 拿上述对add()函数装饰进行修改
def add(a, b):
return a + b
def fun_out(fn, *args, **kwargs):
def fun_inner():
print('函数开始执行')
r = fn(*args, **kwargs)
print(r)
print('函数执行完毕')
return fun_inner
f = fun_out(add, 1, 2)
f()
运行结果 》》》
函数开始执行
3
函数执行完毕
- 写到这里肯定会产生很多问号,为啥子要闭包,还嵌套一个东西那么麻烦,结果效果还一样,就像我刚开始听完这个,其实上面那样用还没有满足我们的需求,因为我们每次需要装饰函数的时候,都得重新修改,很是麻烦,所以我们还要讲一下
@
的用法。
# 通用装饰器
def fun_out(fn, *args, **kwargs):
def fun_inner(*args, **kwargs):
print('函数开始执行')
r = fn(*args, **kwargs)
print(r)
print('函数执行完毕')
return fun_inner
# 需要被装饰的函数
@fun_out # 等价于 f=fun_out(fun)
def add(a, b):
return a + b
add(1, 2)
运行结果 》》》
函数开始执行
3
函数执行完毕
我们举个例子介绍一下为什么要装饰,当一个功能是公用的时候,我们将其写成装饰,就会很方便,比如我们在逛淘宝的时候,不管我们干什么,都需要进行登录,我们就可以把登录的程序写成装饰,然后我们在写其他功能的时候就可以直接@他,直接修改我们需要装饰的函数即可。就不用每写一次都修改各种参数。
PEP8代码编写规范
PEP8: Python代码风格指南——此处引用Cheney老师文章
PEP8 提供了 Python 代码的编写约定. 本节知识点旨在提高代码的可读性, 并使其在各种 Python 代码中编写风格保持一致.
虽然不遵守 PEP8 规范不会报错,但是会出现波浪线,而且遵守了规范看起来也很舒服!!
- 缩进使用4个空格, 空格是首选的缩进方式. Python3 不允许混合使用制表符和空格来缩进.
- 每一行最大长度限制在79个字符以内.
- 顶层函数、类的定义, 前后使用两个空行隔开.
- import 导入
导入建议在不同的行, 例如:
import os
import sys
不建议如下导包
import os, sys
但是可以如下:
from subprocess import Popen, PIPE
- 导包位于文件顶部, 在模块注释、文档字符串之后, 全局变量、常量之前. 导入按照以下顺序分组:
标准库导入
相关第三方导入
本地应用/库导入
在每一组导入之间加入空行 - Python 中定义字符串使用双引号、单引号是相同的, 尽量保持使用同一方式定义字符串. 当一个字符串包含单引号或者双引号时, 在最外层使用不同的符号来避免使用反斜杠转义, 从而提高可读性.
- 表达式和语句中的空格:
避免在小括号、方括号、花括号后跟空格.
避免在逗号、分好、冒号之前添加空格.
冒号在切片中就像二元运算符, 两边要有相同数量的空格. 如果某个切片参数省略, 空格也省略.
避免为了和另外一个赋值语句对齐, 在赋值运算符附加多个空格.
避免在表达式尾部添加空格, 因为尾部空格通常看不见, 会产生混乱.
总是在二元运算符两边加一个空格, 赋值(=),增量赋值(+=,-=),比较(==,<,>,!=,<>,<=,>=,in,not,in,is,is not),布尔(and, or, not
避免将小的代码块和 if/for/while 放在同一行, 要避免代码行太长.
if foo == 'blah': do_blah_thing()
for x in lst: total += x
while t < 10: t = delay()
- 永远不要使用字母 ‘l’(小写的L), ‘O’(大写的O), 或者 ‘I’(大写的I) 作为单字符变量名. 在有些字体里, 这些字符无法和数字0和1区分, 如果想用 ‘l’, 用 ‘L’ 代替.
- 类名一般使用首字母大写的约定.
- 函数名应该小写, 如果想提高可读性可以用下划线分隔.
- 如果函数的参数名和已有的关键词冲突, 在最后加单一下划线比缩写或随意拼写更好. 因此 class_ 比 clss 更好.(也许最好用同义词来避免这种冲突).
- 方法名和实例变量使用下划线分割的小写单词, 以提高可读性.
课后作业
1. 请使用装饰器实现已存在的函数的执行所花费的时间。
- time模块
简单介绍一下time模块
1、延迟功能
import time #一定要导入time模块,下面其他的就不导入了
# 延迟功能
# 想让程序停顿几秒钟
# time.sleep(秒数)
# 例如
print(1)
time.sleep(2)
print(2)
# 结果,在打印了1后会停2秒,然后再打印2
2、获取时间戳
- 时间戳是指格林威治时间自1970年1月1日(00:00:00 GMT)至当前时间的总秒数。
print(time.time())
运行结果 》》》
1612519773.541305
3、时间戳转化为元组
now_time = time.localtime(time.time())
print(now_time)
运行结果 》》》
time.struct_time(tm_year=2021, tm_mon=2, tm_mday=5, tm_hour=18, tm_min=9, tm_sec=33, tm_wday=4, tm_yday=36, tm_isdst=0)
序号 | 属性 | 值 |
---|---|---|
0 | tm_ year | 2021(当年的年份) |
1 | tm_ mon | 1到12 (当前的月份) |
2 | tm_ _mday | 1到31 (当前的天) |
3 | tm_ hour | 0到23 (时) |
4 | tm_ _min | 0到59 (分) |
5 | tm_ sec | 0到61 (60或61是闰秒) (秒) |
6 | tm_ wday | 0到6 (0是周一) (周) |
7 | tm_ yday | 1到366(儒略历) (一年的第多少天) |
8 | tm_ isdst | -1, 0, 1, -1是决定是否为夏令时的旗帜 |
4、格式化时间
res = time.strftime("%Y-%m-%d-%H-%M-%S")
print(res)
运行结果 》》》
2021-02-05-18-47-35
符号 | 含义 |
---|---|
%a | 本地(locale)简化星期名称 |
%A | 本地完整星期名称 |
%b | 本地简化月份名称 |
%B | 本地完整月份名称 |
%c | 本地相应的日期和时间表示 |
%d | 一个月中的第几天(01 - 31) |
%H | 一天中的第几个小时(24 小时制,00 - 23) |
%l | 一天中的第几个小时(12 小时制,01 - 12) |
%j | 一年中的第几天(001 - 366) |
%m | 月份(01 - 12) |
%M | 分钟数(00 - 59) |
%p | 本地 am 或者 pm 的相应符 |
%S | 秒(01 - 61) |
%U | 一年中的星期数(00 - 53 星期天是一个星期的开始)第一个星期天之前的所有天数都放在第 0 周 |
%w | 一个星期中的第几天(0 - 6,0 是星期天) |
%W | 和 %U 基本相同,不同的是 %W 以星期一为一个星期的开始 |
%x | 本地相应日期 |
%X | 本地相应时间 |
%y | 去掉世纪的年份(00 - 99) |
%Y | 完整的年份 |
%z | 用 +HHMM 或 -HHMM 表示距离格林威治的时区偏移(H 代表十进制的小时数,M 代表十进制的分钟数) |
%Z | 时区的名字(如果不存在为空字符) |
%% | %号本身 |
- 接下来我们进入实例(时间戳的使用):
# 求前20个斐波那契数列所需时间
import time
a = time.time()
# 使用函数求前20个斐波那契数列 fun(n)=fun(n-1)+fun(n-2)
def fun(n):
# 基线条件
if n <= 1:
return 1
# 递归条件
return fun(n-1)+fun(n-2)
for i in range(20):
print(fun(i), end=' ')
print() # 换行
b = time.time()
c = round((b-a),3) # 取小数点后3位并四舍五入
print(f'斐波那契数列函数运行一共花了{c}秒')
运行结果 》》》
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765
斐波那契数列函数运行一共花了0.005秒
- 知识点运用及编写思路:
函数用的是上期博客中的求前20个斐波那契数列 。用time模块中时间戳,在函数运行前获取一次时间戳,函数结束后再获取一次时间戳,然后相减,但是他是小数点后6位的一个数字,我们这里用来round函数对其进行小数点后3位的四舍五入,最后得到只用0.005秒就可以运行完此函数。
- 时间戳装饰
# 求前20个斐波那契数列所需时间
import time
def get_time(fun1):
def inner():
now = time.time()
fun1()
end = time.time()
c = round((end - now), 3)
print(f'斐波那契数列函数运行一共花了{c}秒')
return inner
# 使用函数求前20个斐波那契数列 fun(n)=fun(n-1)+fun(n-2)
def fun(n):
# 基线条件
if n <= 1:
return 1
# 递归条件
return fun(n-1)+fun(n-2)
@get_time
def fun1():
list1 = []
for i in range(20):
list1.append(fun(i))
print(list1)
fun1()
运行结果 》》》
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
斐波那契数列函数运行一共花了0.005秒
- 知识点运用及编写思路:
这里我们将遍历也改成一个函数
fun1()
,并将每次调用fun()
函数得到的返回值添加到列表list1[]
,然后对fun1()
进行装饰,如上代码,把获取时间的时间戳写成装饰器,并调用fun1()
函数,fun1()
函数在运行中调用fun()
函数,最后得到效果。
本期没有旺仔注:觉得我写的还行就点个赞吧,您的支持是给我最好的动力!!!ヾ(≧ ▽ ≦)ゝ