zoukankan      html  css  js  c++  java
  • python 装饰器基础

    Python 装饰器

    本文内容参考 fluent python

    装饰器基础知识

    装饰器一般将一个函数替换为另一个函数,举例来说:

    @deco
    def foo():
        print("running foo")
    
    def foo():
        print("running foo")
    foo = deco(foo)
    

    这两种写法的作用是一致的。所以目前可以直观的理解为,装饰器就是在某个函数的外面又包了一层。

    什么时间执行装饰器

    Python装饰器的一个关键特性是:它们在被装饰的函数定义后立即执行。

    也就是说函数装饰器在导入模块时立刻运行,而被装饰的函数只在明确调用时运行。

    tmp = []
    def reg(func):
        print("aaaa")
        tmp.append(func)
        return func
    
    @reg
    def f1():
        print("running f1")
    

    当你在命令行中输入以上代码时,会得到aaaa的输出,即使你还没有明确的调用f1,你会发现其实reg已经执行了,此时查看tmp也会看到f1的信息。而你明确调用f1时,即输入f1(),会得到running f1的输出。

    当然上面只是一个说明装饰器何时执行的demo,在实际使用中,装饰器和被装饰函数往往不在同一个模块中,而且装饰器返回的函数往往是内部定义的,并将其返回。

    实际使用

    这里书中举了一个前文的例子,个人觉得并不需要了解,只需要知道,装饰器可以在这个函数被调用前做一些其他的事情就OK了。

    更常见的使用方式是,装饰器内部定义一个新的内部函数,并将其返回,替代被装饰的函数。说到这里,就不得不先提一下什么是闭包。

    闭包

    先来看一段代码:

    b = 6
    def foo(a=3):
        print(a)
        print(b)
        b = 9
    
    foo()
    

    如果按照C或者C++中的经验,我们会认为输出是3和6,但实际上,是下面的结果:

    3
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 3, in foo
    UnboundLocalError: local variable 'b' referenced before assignment
    

    这是Python的一个设计选择(这不是bug,这是语言特性):Python不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量。

    也就是说,如果我们把b=9这行去掉,那么还是会输出3和6。那么可以通过global关键字让解释器把b当做全局变量。了解完Python的作用域,接下来就开始说明闭包。

    首先说明。闭包与匿名函数不是一个概念,闭包指延伸了作用域的函数,包含在函数定义体中引用、但是不在定义体中定义的非全局变量,关键在于能否访问定义体之外定义的非全局变量。

    举例来说,我们想计算一个不断增长的序列的均值:

    class Averager:
        def __init__(self) -> None:
            self.series = []
    
        def __call__(self, new_value):
            self.series.append(new_value)
            total = sum(self.series)
            return total/len(self.series)
    

    这个实现并不能算错,而是不够pythonic,下面是函数式实现:

    def make_averager():
        series = []
        def averager(new_value):
            series.append(new_value)
            total = sum(series)
            return total/len(series)
        return averager
    
    avg = mkae_averager()
    

    我们在调用avg时,因为make_averager已经返回,所以它的本地作用域被回收,但是实际上series是一个自由变量,并未在本地作用域中绑定。那么averager的闭包延伸到了他自己的作用域之外。

    即,闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用,但是仍可以使用这些自由变量的绑定。

    python3提供了nonlocal关键字,作用是把变量标记为自由变量。具体用处如下:

    def make_averager():
        total = 0
        count = 0
        def averager(new_value):
            count += 1
            total += new_value
            return total / count
        return averager
    

    上面是make_averager的另一种实现,存在问题,这里因为averager内部的count进行了赋值操作,所以解释器默认为局部变量,因此闭包中并未绑定count为自由变量,total同理,此时就可以用nonlocal:

    def make_averager():
        total = 0
        count = 0
        def averager(new_value):
            nonlocal count, total
            count += 1
            total += new_value
            return total / count
        return averager
    

    一个简单的装饰器

    一个输出函数运行时间的装饰器:

    import time
    import functools
    
    def clock(func):
        @functools.wraps(func)
        def clocked(*args, **kw):
            t0 = time.perf_counter()
            res = func(*args, **kw)
            elapsed = time.perf_counter() - t0
            name = func.__name__
            arg_lst = []
            if args:
                arg_lst.append(', '.join(repr(arg) for arg in args))
            if kw:
                pairs = ['%s=%r'%((k, w) for k, w in kw.items())]
                arg_lst.append(', '.join(pairs))
            arg_str = ', '.join(arg_lst)
            print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, res))
            return res
        return clocked
    

    标准库中的装饰器

    lru_cache

    实现了备忘功能,有效减少递归深度。

    singledispatch

    根据第一个参数的类型,以不同的方式执行相同的操作的一组函数。

    参数化装饰器

    因为装饰器本质上是函数,因此,也可以定义一些参数。但这个时候往往需要在装饰器里再多一层嵌套。

    def reg(active=True):
        def deco(func):
            if active:
                func("deco")
            else:
                func("undeco")
            return func
        return deco
    

    deco就是多的那一层嵌套。

  • 相关阅读:
    有关同时包含<winsock2.h>与<windows.h>头文件的问题
    如何使用微软提供的TCHAR.H头文件?
    下面的程序在VC6通过,在VS2008不能,错误信息都是“不能将参数……从const char[]转换为LPCWSTR”
    Android开发学习日志(四)
    爬虫开发(一)
    java集合源码详解
    Paxos算法
    linux 常用命令
    Bitmap的原理和应用
    Flink Checkpoint 问题排查实用指南
  • 原文地址:https://www.cnblogs.com/LuoboLiam/p/15058391.html
Copyright © 2011-2022 走看看