Python8.2

匿名函数

当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。
在Python中,对匿名函数提供了有限支持。还是以map()函数为例,计算f(x)=x2时,除了定义一个f(x)的函数外,还可以直接传入匿名函数:

>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]

通过对比可以看出,匿名函数lambda x: x * x实际上就是:

def f(x):
    return x * x

关键字lambda表示匿名函数,冒号前面的x表示函数参数。
匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。
用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25

同样,也可以把匿名函数作为返回值返回,比如:

def build(x, y):
    return lambda: x * x + y * y

练习

请用匿名函数改造下面的代码:

# -*- coding: utf-8 -*-
----
def is_odd(n):
    return n % 2 == 1

L = list(filter(is_odd, range(1, 20)))
----
print(L)
>>> list(filter(lambda x:x%2==1,range(1,20)))
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

小结

Python对匿名函数的支持有限,只有一些简单的情况下可以使用匿名函数。

装饰器

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

>>> def now():
...     print('2015-3-25')
...
>>> f = now
>>> f()
2015-3-25

函数对象有一个name属性,可以拿到函数的名字:

>>> now.__name__
'now'
>>> f.__name__
'now'

现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:

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

观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:

@log
def now():
    print('2015-3-25')

调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:

>>> now()
call now():
2015-3-25

把@log放到now()函数的定义处,相当于执行了语句:
now = log(now)
由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。
wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

这个3层嵌套的decorator用法如下:

@log('execute')
def now():
    print('2015-3-25')

执行结果如下:

>>> now()
execute now():
2015-3-25

和两层嵌套的decorator相比,3层嵌套的效果是这样的:
now = log(‘execute’)(now)
我们来剖析上面的语句,首先执行log(‘execute’),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。
以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有name等属性,但你去看经过decorator装饰之后的函数,它们的name已经从原来的’now’变成了’wrapper’:

>>> now.__name__
'wrapper'

因为返回的那个wrapper()函数名字就是’wrapper’,所以,需要把原始函数的name等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。
不需要编写wrapper.name = func.name这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

import functools
def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

或者针对带参数的decorator:

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

import functools是导入functools模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上@functools.wraps(func)即可。

通俗理解装饰器

def 炼丹炉(func): # func就是‘孙悟空’这个函数 
    def 变身(*args, **kwargs): #*args, **kwargs就是‘孙悟空’的参数列表,这里的‘孙悟空’函数没有传参数,我们写上也不影响,建议都写上         
        print('有火眼金睛了') # 加特效,增加新功能,比如孙悟空的进了炼丹炉后,  有了火眼金睛技能 
        return func(*args, **kwargs) #保留原来的功能,原来孙悟空的技能,如吃桃子 
return 变身 # 炼丹成功,更强大的,有了火眼金睛技能的孙悟空出世


@炼丹炉
def 孙悟空():
  print('吃桃子')

孙悟空()
# 输出:
# 有火眼金睛了
# 吃桃子
userAge = 40 
def canYou(func): 
def decorator(*args, **kwargs): 
if userAge > 1 and userAge < 10: #加年龄限制,只允许1-10岁才可以看动画片
return func(*args, **kwargs) 
print('你的年龄不符合要求,不能看') 
return decorator 

@canYou 
def play(): 
print('开始播放动画片 《喜洋洋和灰太狼》') 

play() 
# 输出
# 你的年龄不符合要求,不能看 
# 你可以修改上面的 userAge 为9 试试

练习

请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间:

import time,functools
def printcalltime(fun):
    @functools.wraps(fun)
    def wrapper(*args, **kw):
        start = time.time()
        res = fun(*args, **kw)
        end = time.time()
        print('%s excute time = %d ms !' %(fun.__name__, (end - start) * 1000))
        return res
    return wrapper

@printcalltime
def print1():
    time.sleep(2)    #time.sleep()函数推迟调用线程的运行,可通过参数secs指秒数,表示进程挂起的时间。语法:time.sleep(t),t表示推迟执行的秒数


print1()
print(print1.__name__)

结果:
F:\>python hello.py
print1 excute time = 2000 ms !
print1

小结

在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。
decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。

练习

请编写一个decorator,能在函数调用的前后打印出’begin call’和’end call’的日志。

import time,functools
def log(fun):
    @functools.wraps(fun)
    def wrapper(*args, **kw):
        print('begin call')
        r= fun(*args, **kw)
        print('end call')
        return r
    return wrapper

@log
def print1():
    print('36')


print1()

结果:

F:\>python hello.py
begin call
36
end call

思考

能否写出一个@log的decorator,使它既支持:
@log
def f():
pass
又支持:
@log(‘execute’)
def f():
pass

第一种:

import functools
def log(str=None):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            if(str!= None):
                print('%s ' %(str))
            else:
                print('%s' %(func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

@log('execute')
def print1():
    print('36')

@log()
def print2():
    print('236')

print1()
print2()

结果:

F:\>python hello.py
execute
36
print2
236

第二种:

import functools
def log(param):
    if callable(param):             #callable() 函数用于检查一个对象是否是可调用的。
        def wrapper(*args, **kw):
            print('%s' % (param.__name__,))
            param(*args, **kw)
        return wrapper
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (param, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator


@log
def print1():
    print("2018")


@log("execute")
def print2():
    print("2018")


print1() 
print2()

结果:

F:\>python hello.py
print1
2018
execute print2():
2018

猜你喜欢

转载自blog.csdn.net/sinat_36505108/article/details/82025933