1. 闭包
我们都知道,Python函数是支持嵌套的。如果在一个内部函数中对外部作用域(非全局作用域)的变量进行引用,那么内部函数就会被称为闭包。闭包需要满足以下三个条件。存在嵌套关系的函数中。
嵌套的内部函数引用了外部函数的变量。
嵌套的外部函数会将内部函数名作为返回值返回。
为了更好的认识闭包,接下来通过一个案例进行讲解。
#外部函数
def outer(start = 0):
count = [start]#函数内的变量
#内部函数
def inner():
count[0] += 1#引用外部函数的变量
return count[0]
#返回内部函数的名称
return inner
out = outer(5)
print(out())
2~9行代码定义了一个嵌套函数,其中outer函数是外部函数,inner函数是内部函数。
在outer函数中,首先定义了一个列表的变量count(该列表只有一个元素),然后定义了一个inner函数,最后将inner函数的名称返回。
在inner函数中,引用了外部函数的列表count,并对count的元素进行修改,修改后的列表元素使用return返回。
第10行代码将调用外部函数outer的结果赋值给变量out,out引用的是inner函数占用的内存空间。
第11行代码调用了out函数,实际上是inner函数。
运行结果:
下面通过一张图来分析程序执行的过程。调用outer函数,并且把5传递给start参数。此时,start的值为5。
由于start引用的值为5,所以count列表中存放的元素为5。
返回内部函数的名称inner。
将调用outer函数返回的inner赋值给out变量,即out = inner。此时,out变量引用的是inner函数占用的内存空间。
调用out函数,实质上相当于调用inner函数。此时,程序会来到inner函数的定义部分。
让count列表的元素加1,即5+1=6。计算完以后,把得出的结果再赋值为列表的元素。此时,列表中存放的元素为6。
返回count列表的元素值6。
从变量的生命周期角度来讲,在outer函数执行结束以后,变量count已经被销毁了。当outer函数执行完后,会再执行内部的inner函数,由于inner函数中使用了count变量,所以程序应该出现运行时错误。然而,程序仍能正常运行,究其原因主要在于函数的闭包会记得外层函数的作用域,在inner函数(闭包)中引用了外部函数的count变量,所以程序是不会释放这个变量的。
2. 装饰器
在知乎上看到了一个很形象的比喻来描述装饰器,它是这么说的:
“内裤可以来遮羞,但是到了冬天没法为我们防风御寒,聪明的人发明了长裤,有了长裤后就不会再冷了。装饰器就像是这里说的长裤,在不影响内裤作用的前提下,给我们的身体提供了保暖的功能。”
读完上面的句子,不知道有没有体会到装饰器的好处?
2.1 什么是装饰器
装饰器本质是一个Python函数,它可以在不改动其他函数的前提下,对函数的功能进行扩充。通常情况下,装饰器用于下列场景:引入日志;
函数执行时间统计;
执行函数前的预备处理;
执行函数后的清理功能;
权限校验;
缓存。
先看一个简单的例子。
def example():
print("example")
现在有一个要求,希望可以输出函数的执行日志,这是,有人会这么实现:
def example():
print("example")
print("example is running")
example()
但是如果函数example1()、函数example2()都有类似的需求,那么现在这样的做法会出现大量重复的代码。为了减少代码重复,我们可以创建一个新的函数专门记录函数执行日志,谁需要记录执行日志,就把谁作为参数传递。
def printlog(func):
print("函数正在运行中")
func()
def example():
print("example")
printlog(example)
按照上述代码将函数作为参数传递,虽然可以实现功能,但是却破坏了原有代码的逻辑结构。如果要求已经实现的函数,不能修改,只能扩展,即遵守“封闭开放”原则那么是不允许在函数example内部进行修改的。
装饰器可以满足上述要求。在Python中,装饰器的语法是以@开头。
def decorator(func):
print("正在装饰")
def inner():
print("正在验证权限")
func()
return inner
@decorator
def example():
print("example")
example()
下面,我们来分析一下程序的执行过程。
(1)当程序执行到example()是,发现函数example()上面有个装饰器@decorator,所以会先执行@decorator。@decorator等价于example = decorator(example),它可以拆分为两步:
①执行decorator(example),将函数名example作为参数传递给decorator。在调用decorator函数的过程中,首先会执行print语句,输出"正在装饰",然后会将形参func指向example()函数体,并将inner()函数的引用返回给decorator(example),作为decorator(example)的返回值,具体如图:
②将decorator(example)的返回值赋给example,此时example指向inner()函数,如图所示:
到此,我们完成了函数example()的装饰。
(2)调用example()指向的函数。因为example指向的是inner()函数,所以此时,调用example()函数相当于调用inner()函数,输出过程如下:
①输出print语句"正在验证权限"。
②调用func指向的函数体,输出"example"。
2.2 多个装饰器
多个装饰器可以应用在一个函数上,它们的调用顺序是自下而上的。
def decorator1(func):
print("---正在装饰1---")
def inner():
print("---正在验证权限1---")
func()
return inner
def decorator2(func):
print("---正在装饰2---")
def inner():
print("---正在验证权限2---")
func()
return inner
@decorator1
@decorator2
def example():
print("example")
#调用example,调用example之前已经装饰过了
example()
上述代码中,example()函数上面有两个装饰器,按照多个装饰器从下往上的调用顺序,程序首先会执行@decorator2,再执行@decorator1。下面,我们一步步来分析程序的执行过程。
(1)程序将decorator1、decorator2、example函数加载到程序中,加载完成后如图所示:
(2)example()函数上有两个装饰器,首先程序会执行@decorator2,也就是example = decorator2(example),执行完毕后的内存如图所示:
(3)执行@decorator1,也就是example = decorator1(example),这个过程执行完毕后,其实装饰器对函数的装饰已经完成,会输出下列语句:
装饰后的函数关系如图所示:
(4)装饰完毕后,如果使用"example()"语句调用example函数,程序会按照从上向下的顺序运行,最终程序的输出结果为:
2.3 装饰器对有参数函数进行修饰
前面的装饰器都是对无参数的函数进行修饰,如果要对有参数的函数进行修饰,那么参数如何设置呢?
上述代码在执行“test(1,2)”时报错,提示inner()函数不需要参数,但是,我们正在调用时传递了两个参数。下面我们同样画图来分析。
(1)执行@decorator,等价于test = decorator(test),执行完毕后如图所示。
(2)执行代码“test(1,2)”,此时test指向的是inner()函数,程序会执行inner()函数中的代码。可是创建的inner()函数是不需要参数的,因此程序会报错,提示inner不需要参数,但我们却传入了两个参数。
下面对inner()函数进行修改,在创建inner()函数时添加两个参数,再次调用test()函数,具体代码如下:
程序还是报错,同样是缺少参数引起的错误。这是因为,在inner()函数内部执行func()语句是,func()缺少两个参数,具体如图。
再次修改inner()函数,在调用func()时,将a,b参数传入,示例代码如下:
此时,运行结果如下:
上面的装饰器只是对两个参数的函数适用,如果无法确定函数的参数个数以及参数类型,我们可以使用不定长参数来传递,实例代码如下:
此时,调用不同参数的函数,发现装饰器适用于不同参数的函数。
2.4 装饰器对带有返回值的函数进行装饰
前面的装饰器都是对不带返回值的函数进行装饰,如果要对有返回值的函数进行装饰,那么该如何实现?
上述代码创建了一个test()函数,并使用ret变量保存调用test函数后的返回值。此时,如果我们对带有返回值的函数进行修饰,那么,调用test函数后,函数的返回值还能输出吗?
程序执行后,发现此时调用test()函数,函数没有返回值,也就是None。这是因为,当使用@func对test函数装饰后,test指向了func_in()函数,而func_in()函数本省市没有返回值的。
test指向了新的函数,原本test()函数的返回值被functionName()接受了。因此,如果想要输出“Python”需要使用return语句将调用后的结果返回。对装饰器进行修改,修改后的代码如下:
再次调用test()函数,发现成功得到了返回值。
2.5 带有参数的装饰器
前面的装饰器都是不带参数的,这些装饰器最终返回的都是函数名。如果我们给装饰器添加参数,那么需要增加一层封装,先传递参数,然后再传递函数名。
上述代码中,"@func_arg("Python")"装饰函数test,等价于:
test = func_arg("Python")(test)
由于函数func_arg的返回值是func函数的引用,也就是函数名func,因此,上述代码等价于:
test = func(test)
看到这行代码,大家应该很熟悉了,这就是前面我们学习的无参数的装饰器。相比无参数的装饰器,带参数的装饰器只是用来“加强装饰”的,如果希望装饰器可以根据参数的不同,对不同的函数进行不同的装饰,那么带参数的装饰器是个不错的选择。
3 常见Python内置函数
Python提供了很多能实现各种功能的内置函数。内置函数,就是Python中被自动加载的函数,任何时候都可以使用。
3.1 map函数
map函数会根据提供的函数对制定的序列做映射。
map函数的定义如下:
map(function,iterable,...)
第一个参数function表示的是一个函数名,第二个参数iterable可以是序列、支持迭代的容器或迭代器。当调用map函数是,iterable中的每个元素都会调用function函数,所有元素调用function函数返回的结果会保存到一个迭代器对象中。
这里说明一下,在Python 2中,map函数的返回值是列表list类型。
如果希望将迭代器对象转化为列表,则可以通过list函数进行转化。此外,还可以使用for循环直接遍历迭代器对象,从而取出其内部的每个元素。
func = lambda x:x + 2 #匿名函数
result = map(func,[1,2,3,4,5])
print(list(result))
在上述代码中,定义了匿名函数func,接着通过map函数把序列的每个元素取出来,作为参数调用func函数,然后把结果放到result列表中。
程序运行结果为:
通过一张图来描述上述实例执行的原理。
当传入的函数需要两个参数的时候,则需要传递两个列表。
result = map(lambda x,y:x + y,[1,2,3],[4,5,6])
print(list(result))
#结果为[5,7,9]
在Python 3以前,如果调用map函数时传入的function参数为None,那么相当于合并参数为元组。示例(Python 2.7)代码如下:
result = map(None,[1,3,5,7,9],[2,4,6,8,10])
print result
#结果为[(1,2),(3,4),(5,6),(7,8),(9,10)]
如果两个列表的元素个数不一致,那么元素少的序列会以None补齐。示例(Python 2.7)代码如下:
result = map(None,[1,3,5,7,9][2,4,6])
print result
#结果为[(1,2),(3,4),(5,6),(7,None),(9,None)]
在Python 3以后,当map传入的函数为None时,就等同于zip函数的功能,并且已经被zip函数取代了。另外,map函数无法处理两个序列长度不一致、对应位置操作数类型不一致的情况,他们都会报类型错误。
3.2 filter函数
filter函数会对指定序列执行过滤操作。
filter函数的定义如下:
filter(function,iterable)
上述定义中,第一个参数function可以是函数名称或者None,第二个参数iterable可以是序列、支持迭代的容器或迭代器。返回值为迭代器对象(Python 2中,filter函数的返回值是列表类型)。其中,function函数只能接收一个参数,而且该函数的返回值为布尔值。
filter函数的作用是以参数迭代器中的每一个元素分别调用function函数,最后返回迭代器包含调用结果为Ture的元素。
func = lambda x:x % 2
result = filter(func,[1,2,3,4,5])
print(list(result))
上述示例中,定义了匿名函数func,接着通过filter函数把序列的每一个元素取出来,作为参数调用func函数,然后把结果放到result中。
运行结果:
为了让大家理解filter函数的作用,接下来通过一张图来描述上述示例执行的原理。
由图可知,程序把原始序列的元素执行取样操作以后,将不能被2整除(结果为Ture)的元素筛选出来构成一个新列表。
3.3 reduce函数
reduce函数会对参数迭代器中的元素进行累积。
reduce函数的定义如下:
functools.reduce(function,iterable[,initializer])
上述定义中,function是一个带有两个参数的函数;第2个参数iterable是一个迭代器对象;initializer表示固定的初始值。reduce函数会依次从迭代器对象中取出每个元素,和上一次调用function的结果作为参数再次调用function函数。
在Python3中,reduce函数被放置在functools模块中,使用时需要先引入,示例代码如下:
from functools import reduce
func = lambda x,y:x + y
result = reduce(func,[1,2,3,4,5])
print(result)
上述示例中,首先引入了functools模块,然后定义了一个计算两个数的和的匿名函数。由于在调用reduce函数时传入了func和序列,所以程序把序列的每个元素取出来,和上次调用后的结果作为参数再次调用func函数,最后结果将返回给result。
运行结果如下:
如果在第一次调用function函数时,为其提供initializer参数,那么函数会以迭代器中的第一个元素和initializer作为参数进行调用,示例代码如下:
from functools import reduce
result = reduce(lambda x,y:x + y,[1,2,3,4],5)
print(result)
#结果为15
此外,iterable参数还可以传入字符串类型,示例代码如下:
from functools import reduce
result = reduce(lambda x,y:x + y,["aa","bb","cc"],"dd")
print(result)
#结果为"ddaabbcc"
需要注意的是,function函数不能为None。
Python其实没有那么难。