美文网首页
17 | python中的装饰器

17 | python中的装饰器

作者: _咚咚咚东 | 来源:发表于2020-01-13 21:47 被阅读0次

    python中的装饰器

    函数核心回顾

    1.函数是Python中的一等公民,函数也是对象。可以把函数赋值给变量。

    def func(message):
        print('Got a message: {}'.format(message))
    send_message = func
    send_message('hello world')
    
    Got a message: hello world
    

    2.函数可以作为参数,传入到另一个函数中:

    def get_message(message):
        return 'Got a message: ' + message
    
    
    def root_call(func, message):
        print(func(message))
        
    root_call(get_message, 'hello world')
    
    
    Got a message: hello world
    

    3.函数里可以定义函数:

    
    def func(message):
        def get_message(message):
            print('Got a message: {}'.format(message))
        return get_message(message)
    
    func('hello world')
    
    Got a message: hello world
    

    4.函数的返回值可以是函数对象(闭包)

    
    def func_closure():
        def get_message(message):
            print('Got a message: {}'.format(message))
        return get_message
    
    send_message = func_closure()
    send_message('hello world')
    
    
    Got a message: hello world
    

    这里,函数 func_closure() 的返回值是函数对象 get_message() 本身,之后,我们将其赋予变量 send_message,
    再调用 send_message(‘hello world’),最后输出了'Got a message: hello world'。

    装饰器

    
    def my_decorator(func):
        def wrapper():
            print('wrapper of decorator')
            func()
        return wrapper
    
    def greet():
        print('hello world')
    
    greet1 = my_decorator(greet)
    greet1()
    
    wrapper of decorator
    hello world
    

    这里的 greet1 指向warpper函数,warpper函数内有对原函数的调用,之后再进行调用greet1(), 后两行代码可以简写为:

    def my_decorator(func):
        def wrapper():
            print('wrapper of decorator')
            func()
        return wrapper
    
    @my_decorator
    def greet():
        print("hello world")
    
    greet()
    
    wrapper of decorator
    hello world
    
    带参数的装饰器
    
    def repeat(num):
        def my_decorator(func):
            def wrapper(*args, **kwargs):
                for i in range(num):
                    print('wrapper of decorator')
                    func(*args, **kwargs)
            return wrapper
        return my_decorator
    
    
    @repeat(4)
    def greet(message):
        print(message)
    
    greet('hello world')
    
    原函数还是原函数吗?
    greet.__name__
    
    help(greet)
    
    Help on function wrapper in module __main__:
    
    wrapper()
    

    可见,原函数在装饰后,已经被被wrapper函数取代了。为了解决这个问题,我们通常使用内置的装饰器@functools.wrap,它会帮助保留原函数的元信息(也就是将原函数的元信息,拷贝到对应的装饰器函数里)。

    
    import functools
    
    def my_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('wrapper of decorator')
            func(*args, **kwargs)
        return wrapper
        
    @my_decorator
    def greet(message):
        print(message)
    
    greet.__name__
    
    'greet'
    

    类装饰器

    类也可以作为装饰器。类装饰器主要依赖于函数call(),每当你调用一个类的示例时,函数call()就会被执行一次。

    
    class Count:
        def __init__(self, func):
            self.func = func
            self.num_calls = 0
    
        def __call__(self, *args, **kwargs):
            self.num_calls += 1
            print('num of calls is: {}'.format(self.num_calls))
            return self.func(*args, **kwargs)
    
    @Count
    def example():
        print("hello world")
    
    example()
    example()
    
    num of calls is: 1
    hello world
    num of calls is: 2
    hello world
    

    装饰器嵌套

    
    @decorator1
    @decorator2
    @decorator3
    def func():
        ...
    

    执行顺序从里到外,所以上面的语句也等效于下面这行代码:

    decorator1(decorator2(decorator3(func)))
    
    
    import functools
    
    def my_decorator1(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('execute decorator1')
            func(*args, **kwargs)
        return wrapper
    
    
    def my_decorator2(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('execute decorator2')
            func(*args, **kwargs)
        return wrapper
    
    
    @my_decorator1
    @my_decorator2
    def greet(message):
        print(message)
    
    
    greet('hello world')
    

    实例:身份认证

    首先是最常见的身份认证的应用。这个很容易理解,举个最常见的例子,你登录微信,需要输入用户名密码,然后点击确认,这样,服务器端便会查询你的用户名是否存在、是否和密码匹配等等。如果认证通过,你就可以顺利登录;如果不通过,就抛出异常并提示你登录失败。再比如一些网站,你不登录也可以浏览内容,但如果你想要发布文章或留言,在点击发布时,服务器端便会查询你是否登录。如果没有登录,就不允许这项操作等等。

    
    import functools
    
    def authenticate(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            request = args[0]
            if check_user_logged_in(request): # 如果用户处于登录状态
                return func(*args, **kwargs) # 执行函数post_comment() 
            else:
                raise Exception('Authentication failed')
        return wrapper
        
    @authenticate
    def post_comment(request, ...)
        ...
    

    这段代码中,我们定义了装饰器 authenticate;而函数 post_comment(),则表示发表用户对某篇文章的评论。每次调用这个函数前,都会先检查用户是否处于登录状态,如果是登录状态,则允许这项操作;如果没有登录,则不允许。

    实例2:日志记录

    日志记录同样是很常见的一个案例。在实际工作中,如果你怀疑某些函数的耗时过长,导致整个系统的 latency(延迟)增加,所以想在线上测试某些函数的执行时间,那么,装饰器就是一种很常用的手段。
    日志记录同样是很常见的一个案例。在实际工作中,如果你怀疑某些函数的耗时过长,导致整个系统的 latency(延迟)增加,所以想在线上测试某些函数的执行时间,那么,装饰器就是一种很常用的手段。

    
    import time
    import functools
    
    def log_execution_time(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            start = time.perf_counter()
            res = func(*args, **kwargs)
            end = time.perf_counter()
            print('{} took {} ms'.format(func.__name__, (end - start) * 1000))
            return res
        return wrapper
        
    @log_execution_time
    def calculate_similarity(items):
        ...
    

    实例3:输入合理性检查

    在大型公司的机器学习框架中,我们调用机器集群进行模型训练前,往往会用装饰器对其输入(往往是很长的 json 文件)进行合理性检查。这样就可以大大避免,输入不正确对机器造成的巨大开销。

    
    import functools
    
    def validation_check(input):
        @functools.wraps(func)
        def wrapper(*args, **kwargs): 
            ... # 检查输入是否合法
        
    @validation_check
    def neural_network_training(param1, param2, ...):
        ...
    

    实例3:输入合理性检查

    关于缓存装饰器的用法,其实十分常见,这里我以 Python 内置的 LRU cache 为例来说明.
    LRU cache,在 Python 中的表示形式是@lru_cache。@lru_cache会缓存进程中的函数参数和结果,当缓存满了以后,会删除 least recenly used 的数据。
    正确使用缓存装饰器,往往能极大地提高程序运行效率。为什么呢?我举一个常见的例子来说明。
    大型公司服务器端的代码中往往存在很多关于设备的检查,比如你使用的设备是安卓还是 iPhone,版本号是多少。这其中的一个原因,就是一些新的 feature,往往只在某些特定的手机系统或版本上才有(比如 Android v200+)。
    这样一来,我们通常使用缓存装饰器,来包裹这些检查函数,避免其被反复调用,进而提高程序运行效率,比如写成下面这样:

    
    @lru_cache
    def check(param1, param2, ...) # 检查用户设备类型,版本号等等
        ...
    

    这节课,我们一起学习了装饰器的概念及用法。所谓的装饰器,其实就是通过装饰器函数,来修改原函数的一些功能,使得原函数不需要修改。
    这里就充分体现出,函数也是对象的思想。装饰器,使得函数可以像字符、列表等其他数据结构对象一样,可以被一个方法操作。

    
    

    相关文章

      网友评论

          本文标题:17 | python中的装饰器

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