zoukankan      html  css  js  c++  java
  • Python装饰器

    装饰器来自 Decorator 的直译,理解装饰这个词就等于理解了装饰器。

    什么叫装饰,就是装点、提供一些额外的点缀。在 python 中的装饰器则是提供了一些额外的功能。

    函数装饰器

    在学习闭包的时候我们就已经知道,函数是一个对象。

    这意味着函数:

    • 能在函数中定义一个函数
    • 能作为参数传递
    • 能作为返回值

    来看一个简单的例子。

    def decorator(func):
        def wrapper(*args, **kwargs):
            print('123')
            return func(*args, **kwargs)
    
        return wrapper
    
    def say_hello():
        print('同学你好')
    
    say_hello_super = decorator(say_hello)
    say_hello_super()

    结果

    123
    同学你好

    在这段代码中,我们将一个函数 say_hello 作为参数传入函数 decorator,返回一个 wrapper 函数并赋值到 say_hello_super,此时执行 say_hello_super 相当于执行 wrapper 函数。当我们执行 wrapper 函数时会先打印 123 再执行先前传入的 func 参数也就是 say_hello 函数。

    注意 wrapper*args**kwargs 参数,这是必须的, *args 表示所有的位置参数,**kwargs 表示所有的关键字参数。之后再将其传到 func函数中, 这样保证了能完全传递所有参数。

    在这里,decorator 这个函数就是一个装饰器,功能是在执行被装饰的函数之前打印 123

    在 python 中, 有一种语法糖可以代替 say_hello_super = decorator(say_hello) 这一步的操作,以上的代码可以改写成。

    def decorator(func):
        def wrapper(*args, **kwargs):
            print('123')
            return func(*args, **kwargs)
    
        return wrapper
    
    @decorator
    def say_hello():
        print('同学你好')
    
    say_hello()

    结果

    123
    同学你好

    带参数的装饰器

    之前的装饰器是在每次执行函数前打印 123, 如果我们想指定打印的值,那该怎么办?

    def info(value):
        def decorator(func):
            def wrapper(*args, **kwargs):
                print(value)
                return func(*args, **kwargs)
    
            return wrapper
    
        return decorator
    
    @info('456')
    def say_hello():
        print('同学你好')
    
    say_hello()

    结果

    456
    同学你好

    我们可以在装饰器外部再套上一层函数,用该函数的参数接收我们想要打印的数据,并将先前的 decorator 函数作为返回值。这就是之前学到的闭包的一种功能,就是用闭包来生成一个命名空间,在命名空间中保存我们要打印的值 value

    wraps 装饰器

    一个函数不止有他的执行语句,还有着 __name__(函数名),__doc__ (说明文档)等属性,我们之前的例子会导致这些属性改变。

    def decorator(func):
        def wrapper(*args, **kwargs):
            """doc of wrapper"""
            print('123')
            return func(*args, **kwargs)
    
        return wrapper
    
    @decorator
    def say_hello():
        """doc of say hello"""
        print('同学你好')
    
    print(say_hello.__name__)
    print(say_hello.__doc__)

    结果

    wrapper
    doc of wrapper

    由于装饰器返回了 wrapper 函数替换掉了之前的 say_hello 函数,导致函数名,帮助文档变成了 wrapper 函数的了。

    解决这一问题的办法是通过 functools 模块下的 wraps 装饰器。

    from functools import wraps
    
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            """doc of wrapper"""
            print('123')
            return func(*args, **kwargs)
    
        return wrapper
    
    @decorator
    def say_hello():
        """doc of say hello"""
        print('同学你好')
    
    print(say_hello.__name__)
    print(say_hello.__doc__)

    结果

    say_hello
    doc of say hello

    内置装饰器

    有三种我们经常会用到的装饰器, propertystaticmethodclassmethod,他们有个共同点,都是作用于类方法之上。

    property 装饰器

    property 装饰器用于类中的函数,使得我们可以像访问属性一样来获取一个函数的返回值。

    class XiaoMing:
        first_name = '明'
        last_name = '小'
    
        @property
        def full_name(self):
            return self.last_name + self.first_name
    
    xiaoming = XiaoMing()
    print(xiaoming.full_name)

    例子中我们像获取属性一样获取 full_name 方法的返回值,这就是用 property 装饰器的意义,既能像属性一样获取值,又可以在获取值的时候做一些操作。

    staticmethod 装饰器

    staticmethod 装饰器同样是用于类中的方法,这表示这个方法将会是一个静态方法,意味着该方法可以直接被调用无需实例化,但同样意味着它没有 self 参数,也无法访问实例化后的对象。

    class XiaoMing:
        @staticmethod
        def say_hello():
            print('同学你好')
    
    XiaoMing.say_hello()
    
    # 实例化调用也是同样的效果
    # 有点多此一举
    xiaoming = XiaoMing()
    xiaoming.say_hello()
    同学你好
    同学你好

    classmethod 装饰器

    classmethod 依旧是用于类中的方法,这表示这个方法将会是一个类方法,意味着该方法可以直接被调用无需实例化,但同样意味着它没有 self 参数,也无法访问实例化后的对象。相对于 staticmethod 的区别在于它会接收一个指向类本身的 cls 参数。

    class XiaoMing:
        name = '小明'
    
        @classmethod
        def say_hello(cls):
            print('同学你好, 我是' + cls.name)
            print(cls)
    
    XiaoMing.say_hello()
    结果:
    同学你好, 我是小明
    <class '__main__.XiaoMing'>

    类装饰器

    刚刚我们接触的装饰器是函数来完成,实际上由于 python 的灵活性, 我们用类也可以实现一个装饰器。

    类能实现装饰器的功能, 是由于当我们调用一个对象时,实际上调用的是它的 __call__ 方法。

    class Demo:
        def __call__(self):
            print('我是 Demo')
    
    demo = Demo()
    demo()
    结果:
    我是 Demo

    通过这个特性,我们便可以用类的方式来完成装饰器,功能与刚开始用函数实现的一致。

    class Decorator:
        def __init__(self, func):
            self.func = func
    
        def __call__(self, *args, **kwargs):
            print('123')
            return self.func(*args, **kwargs)
    
    @Decorator
    def say_hello():
        print('同学你好')
    
    say_hello()

    用函数来能实现的功能为什么需要类来实现?

    因为通过类我们可以将执行过程拆解到各函数中,降低代码的复杂度,甚至可以通过类属性实现一些复杂功能。

    比如说我们有一些计算耗时很长的函数,并且每次计算的结果不变,那么我们就可以通过类定义一个缓存装饰器,来缓存第一次执行的结果。

    作者:三眼鸭的编程教室
    链接:https://www.zhihu.com/question/26930016/answer/1904166977
    来源:知乎
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    
    import time
    
    class Cache:
        __cache = {}
    
        def __init__(self, func):
            self.func = func
    
        def __call__(self):
    
            # 如果缓存字典中有这个方法的执行结果
            # 直接返回缓存的值
            if self.func.__name__ in Cache.__cache:
                return Cache.__cache[self.func.__name__]
    
            # 计算方法的执行结果
            value = self.func()
            # 将其添加到缓存
            Cache.__cache[self.func.__name__] = value
            # 返回计算结果
            return value
    
    @Cache
    def long_time_func():
        time.sleep(5)
        return '我是计算结果'
    
    start = time.time()
    print(long_time_func())
    end = time.time()
    print(f'计算耗时{end-start}秒')
    
    start = time.time()
    print(long_time_func())
    end = time.time()
    print(f'计算耗时{end-start}秒')

    结果:

    我是计算结果
    计算耗时5.001157283782959秒
    我是计算结果
    计算耗时0.0秒
    转载自链接:https://www.zhihu.com/question/26930016/answer/1904166977
  • 相关阅读:
    树莓派4B对接苹果Homebrigde
    分享一款好看的PyCharm风格(转)
    Centos7安装opencv-python缺少共享库(libSM.so.6, libXrender.so.1, libXext.so.6)的解决办法
    win10 python3.7安装 opencv 和 face_recognition
    Python出现Could not find a version that satisfies the requirement openpyxl (from versions: )
    从零开始开发标准的s57电子海图第十三篇 电子海图中特征记录各字段结构和内容(共一百篇)
    从零开始开发标准的s57电子海图第十二篇 编码原则与记录结构(共一百篇)
    从零开始开发标准的s57电子海图第十一篇--海图文件中的数据类型(共一百篇)
    从零开始开发标准的s57电子海图第十篇--海图文件示例(共一百篇)
    从零开始开发标准的s57电子海图第九篇--数据记录字段DR区的结构(共一百篇)
  • 原文地址:https://www.cnblogs.com/fat-girl-spring/p/15095382.html
Copyright © 2011-2022 走看看