网站LOGO
博客 | 棋の小站
页面加载中
12月6日
达尔达尼亚瀑布,博洛尼亚,意大利 ...
网站LOGO 博客 | 棋の小站
记录学习,心得,状态,生活。
菜单
  • 热评
    用户的头像
    首次访问
    上次留言
    累计留言
    我的等级
    我的角色
    打赏二维码
    打赏博主
    Python高级用法1——装饰器
    点击复制本页地址
    微信扫一扫
    文章二维码
    文章图片 文章标题
    创建时间
  • 一 言
    确认删除此评论么? 确认
  • 本弹窗介绍内容来自,本网站不对其中内容负责。
    按住ctrl可打开默认菜单

    Python高级用法1——装饰器

    · 原创 ·
    学学编程 · Python
    共 5671 字 · 约 5 分钟 · 254

    最近在研究自然语言处理、多线程爬虫,又在使用Flask框架做项目,我突然意识到我已经到了Python的进阶部分了。回顾以往学习Python基础的时候,只学习了Python的基础知识,如类和对象、装饰器、函数等等,但是Python的强大远不止于此。Python有一句话叫一切皆对象,比如函数、类名、就连匿名函数Python也为它们提供了在内存中的栖息之地,因此从现在开始,我就要学习Python的进阶语法了。今天我在本文章记录的就是Python的语法糖,运用了函数闭包的装饰器。

    闭包

    闭包(Closure)是指在一个内部函数中引用了外部函数的变量或参数,并且对该引用进行了保留,使得内部函数可以在外部函数已经执行结束之后仍然可以访问和操作外部函数的变量。闭包可以理解为一个函数和其相关的引用环境组合而成的实体。

    在Python中,当一个函数返回了一个内部函数时,如果内部函数引用了外部函数的变量或参数,那么这个内部函数就是一个闭包。通过闭包,我们可以在函数外部访问到函数内部的变量,即使函数已经执行结束。

    语法糖

    语法糖(Syntactic sugar)是指在编程语言中添加的一种语法结构或表达方式,它不会带来新的功能,只是为了让代码更加可读性好、易于理解和书写。语法糖不会改变语言的底层机制,只是提供了一种更方便的方式来表达某种操作。

    在Python中,装饰器就是一种语法糖。它允许我们使用 @ 符号将装饰器函数应用到目标函数上,而不必显式地调用装饰器函数并将目标函数作为参数传递。通过装饰器,我们可以在不修改目标函数源代码的情况下,增强目标函数的功能或改变它的行为。装饰器为我们提供了一种简洁而优雅的方式来实现代码的重用和增强。

    Python内置装饰器

    我的云笔记中有记录过:使用Property装饰的成员方法会被视为成员属性,如下例:

    python 代码:
    class Square(object):
        def __init__(self, width=0, height=0):
            self.width = width
            self.height = height
    
        @property
        def area(self):
            return self.width * self.height
    
    
    print(Square(2, 3).area)

    在上面的例子中,成员方法area使用了property装饰,它就会当作一个成员属性般使用。而且使用了property修饰的成员函数不能拥有第二个参数。

    自定义装饰器

    不带参数的装饰器

    装饰器实际上就是通过函数闭包实现的,不带参数的装饰器本质上是一个函数对象,它通过参数将被装饰的函数传递到闭包中,在闭包调用被装饰的的函数前后进行了一些处理。

    python 代码:
    import time
    
    def decorator(func):
        def inner():
            start_time = time.time()
            func()
            end_time = time.time()
            print(f'函数{func.__name__}执行花费时间:{end_time - start_time}')
        return inner
    
    @decorator
    def function():
        lst = [i for i in range(1, 10001)]
    
    function()  
    # 函数add执行花费时间0.0000931232137827

    上方代码中,装饰器接收一个参数func,它代表被装饰的函数对象,也就是说此时func就是function函数。所以内部函数inner才会调用func()函数。最后装饰器返回这个内部函数,这个装饰器就可以了。这段代码实际上相当于f = decorator(functon) f()(这个f其实就是inner,它将func替换成了function)。

    它的执行顺序如下:

    • 15行:调用function函数;
    • 11行:调用装饰器;

      • 4行:执行内部函数;
      • 5行:初始时间;
      • 6行:执行func,也就是function函数;

        • 13行:列表推导式
      • 7行:结束时间;
      • 8行:输出;
    • 结束。

    带参数的装饰器

    它要求内部函数和被装饰的函数需要有相同的形参列表,这样在内部函数调用被装饰的函数时才能传递正确的参数。

    python 代码:
    def decorator(func):
        def inner(a, b):
            print('努力计算中...')
            return func(a, b)
        return inner
    
    @decorator
    def add(a, b):
        return a + b
    
    print(add(1, 2))
    # 努力计算中
    # 3

    上方代码中,内部函数接收了两个参数a和b,它们在执行相加运算时会先执行上方的提示语。这段代码的执行逻辑为f = decorator(add) f(a,b)。它的详细执行逻辑如下:

    • 第11行:执行add函数;
    • 第7行:调用装饰器;

      • 第2行:调用内部函数;
      • 第3行:输出“努力计算中”;
      • 第4行:先计算func函数的值即add函数的值再返回;

        • 第8行、第9行:返回a+b,即3;
      • 第4行:return 3;
    • 输出:3

    不定长参数装饰器

    不定长参数装饰器实际上和带参数的装饰器一致,也是需要参数列表相同。

    python 代码:
    def decorator(func):
        def inner(*args, **kwargs):
            print('参数列表:', args, kwargs)
            func(*args, **kwargs)
        return inner
    
    @decorator
    def function(*args, **kwargs):
        print(*args, **kwargs)
    
    function(1, 2, 3, 4, sep=',')

    运行结果:

    运行结果运行结果

    上方代码相当于f = decorator(function) f(*args, **kwargs),运行逻辑不赘述了。

    装饰器带参数

    上方的装饰器后方都是没有括号的,这表明它们仅仅是一个函数对象,但是既然Python可以将函数放在函数里,那么也就说明装饰器也可以放在函数里。换句话说,装饰器带参数就是将装饰器封装在一个函数内,通过该函数的参数对装饰器进行不同的处理,最后让该函数返回该装饰器即可。

    python 代码:
    def pow(pow):
        def decorator(func):
            def inner(a, b):
                print(f'计算({a}+{b})^{pow}')
                return func(a, b) ** pow
            return inner
        return decorator
    
    @pow(2)
    def add(a, b):
        return a + b
    
    print(add(1, 3))
    # 计算(1+3)^2
    # 16

    运行结果:

    运行结果运行结果

    上方代码相当于:f = pow(add) f2 = decorator(f) f2()上方代码的执行逻辑如下:

    • 执行13行:add函数;

      • 执行第9行:pow装饰器;

        • 执行第3行内部函数;
        • 输出:“计算(1+3)^2”(^是乘方的意思)
        • 先执行func(a, b),即add(1,3)

          • 第11行,返回1+3,即4
        • 再执行后面的乘方,即4 ** 4,为16
        • return 16
    • 输出:“16”
    注:上方代码我都以函数调用作为程序的入口,在这之前省略了定义装饰器以及调用装饰器函数的过程。实际上,由于Python从上到下的执行顺序,因为最先定义的是装饰器,因此会执行装饰器的def代码,然后是使用@符号的装饰器,在这步由于装饰器也是函数,所以会执行装饰器内部的代码,最后返回内部函数,在下方多重装饰器会演示装饰器在初始化时的执行顺序。

    多重装饰器

    若一个函数被多个装饰器装饰,按照语法糖的规则,执行顺序是先执行离函数近的装饰器,在执行离函数远的装饰器。

    python 代码:
    def decorator1(func):
        print('装饰器1')
        def inner(html):
            print('处理函数1')
            return '<div>' + func(html) + '</div>'
        return inner
    
    def decorator2(func):
        print('装饰器2')
        def inner(html):
            print('处理函数2')
            return '<p>' + func(html) + '</p>'
        return inner
    
    @decorator1
    @decorator2
    def f(html):
        print('函数f')
        return html
    
    print(f('执行函数f'))
    # 装饰器2
    # 装饰器1
    # 处理函数1
    # 处理函数2
    # 函数f
    <div><p>执行函数f</p></div>

    上方代码相当于f1 = decorator2(f) f2 = decorator1(f1) f2()。这个地方由于函数的嵌套关系,光用文字描述解释不清楚。换句话说,由上方代码的等效表达式,可得这段代码的嵌套关系为f(decorator1(decorator2(html))),所以函数会先计算decorator2(html)的值,为'<p>执行函数f</p>',再执行decorator1('<p>执行函数f</p>'),结果为<div><p>执行函数f</p></div>

    装饰器类

    只要实现了魔法函数__call__(self)方法,该类就可以作为装饰器类。

    若一个类实现了魔法函数__call__(self),这个类的实例就可以作为函数被调用,但一般只要该类存在魔法函数__call__(self),该类大概率就为装饰器类。
    python 代码:
    class Decorator(object):
        def __init__(self, func):
            self.func = func
    
        def __call__(self, *args, **kwargs):
            print(f'{self.func.__name__}函数的装饰器被调用')
            self.func(*args, **kwargs)
    
    @Decorator
    def f():
        print('函数f被调用')
    
    f()
    
    # f函数的装饰器被调用
    # 函数f被调用

    运行结果:

    运行结果运行结果

    本次学习部分内容来源于文章Python自定义装饰器

    声明:本文由 (博主)原创,依据 CC-BY-NC-SA 4.0 许可协议 授权,转载请注明出处。
    现在已有

    8

    条评论
    发一条!
    1. 头像
      Fgaoxing
      • 等级:Lv.3
      • 角色:访客
      • 在线:很久之前

      装饰器本身就一个参数,传进去函数,出来也是

      · · · 黑龙江-哈尔滨
      1. 头像
        Fgaoxing

        不是吧,传出来你指的是返回值吗,返回值应该是内部函数呀,如果返回值是传进去的参数那不就没对原函数做任何处理嘛

        · · · 辽宁-沈阳
        1. 头像
          Fgaoxing
          • 等级:Lv.3
          • 角色:访客
          • 在线:很久之前

          额,这里的函数是个类型

          · · · 黑龙江-哈尔滨
          1. 头像
            Fgaoxing

            不好意思还是不明白你说的是啥意思

            · · · 辽宁-沈阳
            1. 头像
              Fgaoxing
              • 等级:Lv.3
              • 角色:访客
              • 在线:很久之前

              都是函数类型,但是不是一个函数(传出和传入)

              · · · 黑龙江-哈尔滨
            2. 头像
              Fgaoxing
              • 等级:Lv.3
              • 角色:访客
              • 在线:很久之前

              就是说,函数是个类型,不是说具体某个函数,这里指的是type都是函数

              · · · 黑龙江-哈尔滨
              1. 头像
                Fgaoxing

                · · · 辽宁-沈阳
              2. 头像
                Fgaoxing

                大概懂你的意思了,你的意思就是传进去的参数需要是某个函数类的实例

                · · · 辽宁-沈阳
    博客logo 博客 | 棋の小站 记录学习,心得,状态,生活。
    ICP 冀ICP备2023007665号 ICP 冀公网安备 13030202003453号

    🕛

    本站已运行 221 天 15 小时 46 分

    👁️

    今日访问量:481 昨日访问量:2564

    🌳

    建站:Typecho 主题:MyLife
    博客 | 棋の小站. © 2023 ~ 2023.
    网站logo

    博客 | 棋の小站 记录学习,心得,状态,生活。
     
     
     
     
    壁纸
     
     
     
     

    8

    1

  • 下一篇