python中定义的函数不掉用不会执行_学不会的Python函数——高级函数

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其实没有那么难。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值