python装饰器入门

python装饰器

函数也是对象

在python中,function是一级对象,这也就意味着function可以作为arguments一样传递,就像其它的对象一样(字符串,整型变量,浮点型变量,列表等等)。当函数名像其它类型的变量一样作为另一个函数的参数被传递时,实现了函数的嵌套调用。

def say_hello(name):
    return f"Hello {name}"

def be_awesome(name):
    return f"Yo {name}, you are awesome!"

def greet_bob(greeter_func):
    return greeter_func("Bob")

print(greet_bob(say_hello))
print(greet_bob(be_awesome))
Hello Bob
Yo Bob, you are awesome!

函数内部定义的函数成为inner function, 内部函数。内部函数只存在在局部空间,也就是说只能在定义的函数中被调用,下面的例子就说明了这一点。

def parent():
    print('The parent() function')
    
    def first_child():
        print('The first_child() function')
    
    def second_child():
        print('The second_child() function')
        
    second_child()
    first_child()

parent()
The parent() function
The second_child() function
The first_child() function

函数名也可以用作返回参数。这时,内部定义的函数也可以被外部访问到,可以当作一个普通函数调用,也就是要加上()。

def parent(num):
    def first_child():
        return "HI I am Emma"
    def second_child():
        return "HI I am Lin"
    if num == 1:
        return first_child
    else: 
        return second_child

first_child = parent(1)
second_child = parent(2)

print(first_child()) # 注意不是print(first_child)
print(second_child()) 

HI I am Emma
HI I am Lin
print(first_child)
<function parent.<locals>.first_child at 0x1115e74d0>

简单装饰器

理解了函数是python对象,函数名可以作为参数传来传去这个点后,我们下面来个稍微有点难度的例子,let’s go!

def my_decorator(func):
    def wrapper():
        print("Before the function is called")
        func()
        print("After the function is called")
        
    return wrapper

def say_whee():
    print("whee")
    
say_whee = my_decorator(say_whee)


say_whee()
Before the function is called
whee
After the function is called

say_whee = my_decorator(say_whee)这一句就是应用了所谓的函数装饰器。事实上,这里把say_whee函数名作为my_decorator的输入参数,之后将返回的函数名wrapper赋予了最左侧的say_whee变量。这时候,最左侧的say_whee实际上指向了my_decorator的内部函数wrapper()。

简单滴说,就是“装饰器“是封装了一系列函数的行为,将被装饰的函数嵌入到这些行为中。(decorators wrap a function, modifying its behavior)”
下面的例子中,函数被限制在了工作时间执行(7到22点),首先看看如果用普通的函数定义:

from datetime import datetime

def not_during_the_night(func):
    def wrapper():
        if 7 <= datetime.now().hour < 22:
            func()
        else:
            pass
    return wrapper

def say_whee():
    print("Whee!")

say_whee = not_during_the_night(say_whee)
say_whee()  
Whee!

装饰器@ 语法给上面的例子提供了更加简介的解决方案。值得注意的是,装饰器构造函数一定要返回行为定义内部函数的函数名,装饰器使用的时候是在新函数定义的前一行,用@符号后面跟着装饰器函数名,不加()。例如:

@not_during_the_night
def say_whee_in_work_hours():
    print("Whee")
    
say_whee_in_work_hours()
Whee
def do_twice(func):
    def wrapper_do_twice():
        func()
        func()
    return wrapper_do_twice

@do_twice
def say_whee():
    print("Whee")

say_whee() 
Whee
Whee

被修饰函数有参数的情况

上面的例子中,被修饰的函数都是没有输入和输出参数的。我们先来看看下面的这个例子:

@do_twice
def greet(name):
    print(f"Hello {name}")

greet("Alice")

报错:greet() missing 1 required positional argument: ‘name’
这是什么情况?回忆一下装饰器的工作原理,在函数执行的时候,实际上是传给了do_twice装饰器函数,在这个函数内部再执行greet这个函数,但是这时greet函数没有输入参数。怎么解决问题?这时应该用到python的可变参数。

def do_twice_with_args(func):
    def wrapper_do_twice(*args,**kwargs):
        func(*args,**kwargs)
        func(*args,**kwargs)
        #return wrapper_do_twice #报错:'NoneType' object is not callable
    return wrapper_do_twice

@do_twice_with_args
def greet(name):
    print(f"Hello {name}")
    
greet("Alice")
Hello Alice
Hello Alice

如果函数有返回值呢?

@do_twice_with_args
def greet_back(name):
    return f"Hello {name}"
print(greet_back("Ben"))
None

返回的是None。显然装饰函数运行过程中“吞”掉了greet_back函数的输出,就在装饰器内部函数wrapper_do_twice()内部,调用传入的函数时,没有返回值。我们修改一下do_twice_with_args的定义,在wrapper_to_twice函数后面添加一个return语句。
事实上,之前返回的None是python函数在用户没有定义return语句时默认返回的值。

def do_twice_with_args(func):    
    def wrapper_do_twice(*args, **kwargs):  
        func(*args, **kwargs)
        return func(*args, **kwargs)        
    return wrapper_do_twice

@do_twice_complex
def greet_back(name):
    print('Hello, Alice')
    return f"Hello, {name}"

def no_return():
    print()
    print('default return should be ')
    

hi = greet_back("Ben")
print(hi)

ret = no_return()
print(ret)
Hello, Alice
Hello, Alice
Hello, Ben

default return should be 
None

通常我们用__定义对象的内部变量和内部函数,python的这一机制便于查看对象的基本情况,还可以用内置的help函数查看对应的帮助文档。例如:

print("print.__name__:   "print.__name__)
print(help(print))
print
Help on built-in function print in module builtins:

print(...)
    print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
    
    Prints the values to a stream, or to sys.stdout by default.
    Optional keyword arguments:
    file:  a file-like object (stream); defaults to the current sys.stdout.
    sep:   string inserted between values, default a space.
    end:   string appended after the last value, default a newline.
    flush: whether to forcibly flush the stream.

None

但是在装饰器语法装饰后,会让被装饰函数丧失这个特性,有的时候会给代码编写和测试带来麻烦,如何修复这个问题?
可以用functools工具包解决。例如:

print('greet_back.__name__:  '+greet_back.__name__)
print(help(greet_back))
print()
print("import functools toolbox to fix this. ")

import functools

def do_twice(func):
    @functools.wraps(func)
    def wrapper_do_twice(*args, **kwargs):
        func(*args, **kwargs)
        return func(*args, **kwargs)
    return wrapper_do_twice

@do_twice
def greet(name):
    print('Hi! ')
    return f"Hello, {name}"

print(greet("Ti-Ya"))
print('greet.__name__:  '+greet.__name__)

greet_back.__name__:  wrapper_do_twice
Help on function wrapper_do_twice in module __main__:

wrapper_do_twice(*args, **kwargs)

None

import functools toolbox to fix this. 
Hi! 
Hi! 
Hello, Ti-Ya
greet.__name__:  greet

到这里,python装饰器的定义和基本使用就讲完了。下面给出实际中的例子。

装饰器的应用

装饰器函数代码的一般结构

首先来总结下一个装饰器函数的一般写法:

import functools

def decorator(func):
    @functools.wraps(func)
    def wrapper_decorator(*args, **kwargs):
        # Do something before
        value = func(*args, **kwargs)
        # Do something after
        return value
    return wrapper_decorator

记时装饰器

记录函数运行时间

## 记时装饰器
import functools
import time

def timer(func):
    """Print the run time of the decorated function."""
    @functools.wraps(func)
    def wrapper_timer(*args, **kwargs):
        start_time = time.perf_counter()
        value = func(*args, **kwargs)
        end_time = time.perf_counter()
        run_time = end_time - start_time
        print(f"Fininshed {func.__name__} in {run_time:.4f} secs")
        return value
    return wrapper_timer

import tensorflow as tf
import numpy as np
@timer
def tensor_computation():
    a = tf.constant(np.random.randn(3,3))
    b = tf.constant(np.random.randn(3,3))
    c = tf.matmul(a,b)
    return c

tensor_computation()
Fininshed tensor_computation in 0.0002 secs





<tf.Tensor: shape=(3, 3), dtype=float64, numpy=
array([[-0.67434088,  1.34905892,  0.59164407],
       [-2.14192767,  1.13678956, -0.48185652],
       [-0.6749793 ,  1.35129415, -1.46809712]])>

debug模式装饰器

可以定义debug装饰器时对函数的执行情况进行检查,这里给个简单的例子。repr()是python的内置函数,和str()用法类似但又有相同。详见:https://www.geeksforgeeks.org/str-vs-repr-in-python/

此外,f""字符串中的!r代表按照两边带引号的字符串进行打印。关于f-string更多的说明和帮助在这里:
https://www.python.org/dev/peps/pep-0498/#s-r-and-a-are-redundant

import functools

def debug(func):
    """Print the function signature and return value"""
    def wrapper_debug(*args,**kwargs):
        args_repr = [repr(a) for a in args]
        kwargs_repr = [f"{k}={v!r}" for k,v in kwargs.items()]
        signature = ','.join(args_repr+kwargs_repr)
        print(f"Calling {func.__name__}({signature})")
        value = func(*args, **kwargs)
        print(f"{func.__name__!r} returned {value!r}")
        return value
    return wrapper_debug

@debug
def make_greeting(name, age=None):
    if age is None:
        return f"Howdy {name}!"
    else:
        return f"Whoa {name}! {age} already, you are growing up!"
    
make_greeting("Benjamin")
make_greeting("Richard", age=112)

print()
rname = "Neptune"
print(f"{rname!r} is a cute boy!")
Calling make_greeting('Benjamin')
'make_greeting' returned 'Howdy Benjamin!'
Calling make_greeting('Richard',age=112)
'make_greeting' returned 'Whoa Richard! 112 already, you are growing up!'

'Neptune' is a cute boy!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值