美文网首页
廖雪峰 | 5.2 装饰器和偏函数

廖雪峰 | 5.2 装饰器和偏函数

作者: 苦哈哈的柠檬水 | 来源:发表于2022-04-20 11:36 被阅读0次

    装饰器

    1,装饰器定义:在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。本质上,decorator就是一个返回函数的高阶函数。
    例子:假如要增强sum()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改sum()函数的定义,则可以使用“装饰器”(Decorator)
    2,实例
    (1)函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。函数对象有一个__name__属性,可以拿到函数的名字:

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

    (2)定义一个能打印日志的decorator,增强now()函数的功能,可以定义如下:

    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()
    call now():
    2015-3-25
    
    • @log放到now()函数的定义处,相当于执行了语句:
    now = log(now)
    
    • wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。
      (3)如果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函数。
    (4)装饰器的属性复制:functools.wraps
    经过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper'

    >>> now.__name__
    'wrapper'
    

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

    #不带参数的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
    

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

    # -*- coding: utf-8 -*-
    import time, functools
    def metric(fn):
        @functools.wraps(fn)
        def wrapper(*arg, **kw):
            start = time.time()
            res = fn(*arg, **kw)
            print('%s executed in %s ms' % (fn.__name__, time.time()-start))
            return res
        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('测试失败!')
    

    4,练习二
    问:请编写一个decorator,能在函数调用的前后打印出begin callend call的日志

    ?
    

    5,练习三
    问:写出一个@logdecorator,使它既支持不带参数打印日志的decorator,又支持带参数的可打印日志的decorator

    import functools
    def log(text):
        def decorator(fn):
            @functools.wraps(fn)
            def wrapper(*arg, **kw):
                if type(text) != str:
                    print('execute %s(): ' %fn.__name__)
                else:
                    print('%s %s(): ' %(text, fn.__name__))
                return fn(*arg, **kw)
            return wrapper
        if type(text) == str:
            return decorator
        else:
            return decorator(text)
    
    @log
    def f1():
        print('可以@log')
    
    @log('execute')
    def f2():
        print('可以@log()')
    
    f1()
    f2()
    

    偏函数

    1,偏函数(Partial function)
    属于Python的functools模块的功能,通过设定参数的默认值,从而降低函数调用的难度
    2,解释实例
    int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换。如果传入base参数,就可以做N进制的转换:

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

    可以定义一个int2()的函数,默认把base=2传进去,也可以用functools.partial创建一个偏函数:

    def int2(x, base=2):
        return int(x, base)
    
    >>> import functools
    >>> int2 = functools.partial(int, base=2)
    
    >>> int2('1000000')
    64
    
    • functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
    • 新的int2函数,仅仅是把base参数重新设定默认值为2,但也可以在函数调用时传入其他值:
    >>> int2('1000000', base=10)
    1000000
    
    >>> int2('1000000',10)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: int() takes at most 2 arguments (3 given)
    

    3,创建偏函数时,实际上可以接收函数对象、*args**kw这3个参数,当传入:

    int2 = functools.partial(int, base=2)
    

    实际上固定了int()函数的关键字参数base,也就是:

    int2('10010')
    #相当于
    kw = { 'base': 2 }
    int('10010', **kw)
    

    max()函数

    max2 = functools.partial(max, 10)
    max2(5, 6, 7)
    #相当于
    args = (10, 5, 6, 7)
    max(*args)
    
    >>> max2(5, 6, 7)
    10
    

    相关文章

      网友评论

          本文标题:廖雪峰 | 5.2 装饰器和偏函数

          本文链接:https://www.haomeiwen.com/subject/wbasertx.html