Python 匿名函数,装饰器和偏函数

详细内容请参考廖雪峰官网,此处只是一些摘抄,心得与练习的coding。

  1.  匿名函数
    其实就是lambda.
    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)))
    L = list(filter(lambda n:n%2 == 1,range(1,20)))
    
    print(L)

     

  2. 装饰器
    我觉得这是Python一个很牛的发明。就是在不改变现在代码的情况下,在函数名称上一行加上@装饰器,就可以达到改变函数的效果。有点像把函数的指针重新指向了另一个地方。
    比如现在有一个now()函数:
    >>> def now():
    ...     print('2018-8-19')
    ...
    >>> f = now
    >>> f()
    2018-9-19

    假设我们要增强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('2018-8-19')

    调用now()函数:

    >>> now()
    call now():
    2018-8-19

    @log放到now()函数的定义处,相当于执行了语句: now = log(now)
    由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。
    wrapper()函数的参数定义是(*args, **kw),因此,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('2018-8-19')

    执行结果如下:

    >>> now()
    execute now():
    2018-8-19

    和两层嵌套的decorator相比,3层嵌套的效果是这样的:

    >>> now = log('execute')(now)

    因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

    不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的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


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

    # -*- coding: utf-8 -*-
    import time, functools
    def metric(fn):
        @functools.wraps(fn)
        def wrapper(*args,**kw):
            gap_begin = time.time()
            r = fn(*args,**kw)
            gap_end = time.time()
            print('%s executed in %.2f ms' % (fn.__name__, gap_end-gap_begin))
            return r
        return wrapper
    
    # 测试
    @metric
    def fast(x, y):
        time.sleep(0.0012)
        return x + y;
    
    @metric
    def slow(x, y, z):
        time.sleep(0.1234)
        return x * y * z;
    
    f = fast(11, 22)
    s = slow(11, 22, 33)
    if f != 33:
        print('测试失败!')
    elif s != 7986:
        print('测试失败!')
    
    print(fast.__name__)
    print(slow.__name__)

     

  3. 偏函数

    这里的偏函数和数学意义上的偏函数不一样。在介绍函数参数的时候,通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点.
    例如: int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换:

    >>> int('12345', base=8)
    5349
    >>> int('12345', 16)
    74565

    假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:

    def int2(x, base=2):
        return int(x, base)

    functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2

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

    所以,简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
    最后,创建偏函数时,实际上可以接收函数对象、*args**kw这3个参数,当传入:

    max2 = functools.partial(max, 10)

    实际上会把10作为*args的一部分自动加到左边,也就是:

    max2(5, 6, 7)

    相当于:

    args = (10, 5, 6, 7)
    max(*args)

    结果为10


     



     

 

posted @ 2018-08-19 21:35  ChrisK  阅读(190)  评论(0编辑  收藏  举报