Python自学记录——返回函数、匿名函数、装饰器与偏函数

国庆节快乐~~虽说今天是假期的最后一天。。

好久没学习Python了。。值得高兴的是 《怪物猎人:世界》目前所有的龙我(统枪)都打过一遍了 (/得意)。

正题,开始学习、记录:

返回函数

顾名思义,返回函数即 返回值为函数。调用一个函数,返回另一个函数,当执行另一个函数时,另一个函数的内部才执行。有点绕,示例如下:

>>> def test(alist):
...     def sum():
...             s = 0
...             for x in alist:
...                     s = s + x
...             return s
...     return sum
...
>>> L = [1,3,5,7,9]
>>> c = test(L)
>>> c
<function test.<locals>.sum at 0x000001B48D574488>
>>> c()
25

每次调用这个函数都会生成一个新的函数,函数不相等,函数的结果相等,接着上面的示例,如下:

>>> c1 = test(L)
>>> c2 = test(L)
>>> c1 == c2
False
>>> c1() == c2()
True

学习到这里时,涉及到了一个新的概念,名为"闭包"。

我理解的 闭包 即 调用一个函数所返回的函数里包含参数及变量,返回的函数是一个待执行完成的函数,只要调用就会根据其内部的元素结构返回结果。

这样的话会产生一个问题,示例如下:

>>> def count():
...     f = []
...     for i in range(1, 4):
...         def ft():
...              return i*i
...         f.append(ft)
...     return f
...
>>> f = count()
>>> for x in f:
...     x()
...
9
9
9
>>> f[0]
<function count.<locals>.f at 0x000001B48D5746A8>

上述示例中,首先看起来返回的是 列表f ,不是函数。实际上函数是存放在 列表f 中,返回的是个函数列表;其次,遍历执行列表中的函数,值相同。想要的结果 是返回 1、4、9 ,不是 9、9、9。

由于在函数中定义了 变量 i ,i 在不断循环获取新的值,所生成的函数并没有执行运算。当循环完成,在开始执行运算时,i指向的变量值为循环所得的最后一个值,即3,所以值都为9。

想要得到结果为 1、4、9,需要针对循环的值做下处理,示例如下:

>>> def count():
...     def f(j):
...         def g():
...             return j*j
...         return g
...     fs = []
...     for i in range(1, 4):
...         fs.append(f(i)) 
...     return fs
...
>>> f = count()
>>> for x in f:
...     x()
...
1
4
9

返回函数在返回前执行了运算,将 i 当前的值传入了返回函数中。

练习题:利用闭包返回一个计数器函数,每次调用它返回递增整数。自解法如下:

>>> def createCounter():
...     a=[0]
...     def counter():
...             a[0] += 1
...             return a[0]
...     return counter
...
>>> c = createCounter()
>>> c()
1
>>> c()
2
>>> c()
3

此题中还有其他解法,涉及到了变量范围问题,在这里把我理解的简单说下:

nonlocal

让内部函数获取并使用其外层函数定义的变量,以练习题示例如下:

>>> def createCounter():
...     a = 0
...     def counter():
...         nonlocal a
...         a += 1
...         return a
...     return counter
...
>>> c = createCounter()
>>> c()
1
>>> c()
2

global

内部想要修改全局变量时,需要在局部先声明全局变量,示例如下:

>>> a = 0
>>> def createCounter():
...     def counter():
...             global a
...             a += 1
...             return a
...     return counter
...
>>> c = createCounter()
>>> c()
1
>>> c()
2
>>> c()
3

匿名函数

顾名思义,即没有名字的函数。这种函数有个好处,就是不用担心函数命名冲突的问题,使用方式如下所示:

>>> t = lambda x: x*x
>>> t
<function <lambda> at 0x000001B48D574488>
>>> t(10)
100
# lambda x: x*x 为匿名函数,它等于下方函数
>>> def ft(x):
...     return x*x

关键字 lambda 表示为匿名函数,匿名函数有限制,只能有一个表达式,不需要写return ,返回值即表达式结果。

它的用法也很灵活,比如写在函数的返回值里等等。

装饰器

装饰器(Decorator),简单来说,就是在不改变原函数的情况下,动态加强函数的功能。

由于函数也是一个对象,而且函数对象可以赋值给变量,所以也能通过变量调用函数。

在Python的函数对象中,有 " __name__ " 属性(下划线前面两个,后面两个),调用它可以得到函数的名字,示例如下所示:

>>> def test():
...     print('Hello world!')
...
>>> test.__name__
'test'
>>> a = test
>>> a.__name__
'test'

下面写一个装饰器(Decorator),实现在调用函数时打印一条语句。本质上,装饰器就是一个返回函数,示例如下:

>>> def log(func):
...     def wrapper(*arg,**kw):
...             print('call %s:' % func.__name__)
...             return func(*arg,**kw)
...     return wrapper
...

如上所示,log函数接收一个函数,也返回一个函数。使用它需要借助Python中的@语法,示例如下:

>>> @log
... def now():
...     print('2018-10-07')
...
>>> now()
call now:
2018-10-07

如上所示,在要调用的函数上一行加 @和 装饰器名称,在调用函数时便自动打印出了一句事先写好的话。

此时,将此函数赋值给一个变量,打印变量的函数名,如下所示:

>>> tname = now
>>> tname.__name__
'wrapper'

其实,把装饰器放在函数定义上方,相当于执行了:

now = log(now)

执行 now函数前,Python生成一个同函数名的变量,并让它等于执行传入函数的装饰器后返回函数。看装饰器的代码部分,log函数先执行了wrapper函数,输出了一条语句,之后返回执行传入的函数;再之后返回一个wrapper函数。

如果装饰器需要传参的话,会比较复杂,但也是个返回函数,示例如下:

>>> def log(test):
...     def decotator(func):
...             def wrapper(*args,**kw):
...                     print('%s %s:' % (test,func.__name__))
...                     return func(*args,**kw)
...             return wrapper
...     return decotator
...
>>> @log('The test')
... def now():
...     print('2018-10-07')
...
>>> now()
The test now:
2018-10-07

现在还有一个重要的问题,调用当前函数返回的函数名是 wrapper ,而不是当前函数名。上述代码中当前函数名等其他函数属性并未复制到 wrapper函数中,若代码中有函数依赖函数的签名,则会出错。Python中内置的 functools.wraps 可以解决这个问题,示例如下:

>>> import functools
>>> def log(func):
...     @functools.wraps(func)
...     def wrapper(*args,**kw):
...             print('call %s:' % func.__name__)
...             return func(*args,**kw)
...     return wrapper
...
>>> @log
... def now():
...     print('2018-10-07')
...
>>> now()
call now:
2018-10-07
>>> tnow = now
>>> tnow.__name__
'now'

同理,带参数的装饰器如下所示:

>>> import functools
>>> def log(text):
...     def decorator(func):
...             @functools.wraps(func)
...             def wrapper(*args,**kw):
...                     print('%s %s' % (text,func.__name__))
...                     return func(*args,*kw)
...             return wrapper
...     return decorator
...
>>> @log('Good')
... def now():
...     print('2018-10-07')
...
>>> now()
Good now
2018-10-07
>>> tnow = now
>>> tnow.__name__
'now'

回顾下知识点,%s为占位符号,详情在这里

偏函数

简单来说,偏函数就是给函数设置默认值,使得后面写代码时调用函数不用总输入固定值,示例如下:

>>> int('123')
123
>>> int('123',base=8)
83
>>> def int2(x,base=2):
...     return int(x,base)
...
>>> int2('1000000')
64

如上所示,这样调用函数就方便了。在Python中我们可以用functools.partial直接生成一个偏函数,不需要我们自己定义,示例如下:

>>> import functools
>>> int2 = functools.partial(int,base=2)
>>> int2('1000000')
64

本篇就到这里,教材网址:https://www.liaoxuefeng.com, 继续学习~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章