首页 > 编程语言 >python 装饰器

python 装饰器

时间:2022-12-03 00:44:24浏览次数:34  
标签:__ return python wrapper add print 装饰 def

def add(x, y):
    return x + y


def logger(fn):
    def wrapper(*args, **kwargs):
        print('调用前增强')
        ret = fn(*args, **kwargs)  # 参数解构
        print('调用后增强')
        return ret

    return wrapper


inner = logger(add)
x = inner(4, 5)
print(x)

#运行结果
调用前增强
调用后增强
9

可以进一步修改

def add(x, y):
    return x + y


def logger(fn):
    def wrapper(*args, **kwargs):
        print('调用前增强')
        ret = fn(*args, **kwargs)  # 参数解构
        print('调用后增强')
        return ret

    return wrapper


add = logger(add)
x = add(4, 5)
print(x)

添加地址查询

便于理解

def add(x, y):
    return x + y


def logger(fn):
    def wrapper(*args, **kwargs):
        print('调用前增强')
        print('wrapper的地址:',id(wrapper))
        ret = fn(*args, **kwargs)  # 参数解构
        print('fn此时的地址', id(fn))
        print('调用后增强')
        return ret

    return wrapper


print('老add地址:', id(add))
add = logger(add)#先运行右边的,已经将老add地址通过传参,已经传给了logger函数的形参fn,返回的wrappr地址赋值给了add,覆盖了老add,相当于新建了一个新标识符
print('定义后的add地址:', id(add))
x = add(4, 5)#此时相当于wrappr(4,5)
print('定义后的add地址:', id(add))
print(x)

#运行结果
老add地址: 1772409699592
定义后的add地址: 1772409699864
调用前增强
wrapper的地址: 1772409699864
fn此时的地址 1772409699592
调用后增强
定义后的add地址: 1772409699864
9

用装饰器来改进

def logger(fn):
    def wrapper(*args, **kwargs):
        print('调用前增强')
        ret = fn(*args, **kwargs)  # 参数解构
        print('调用后增强')
        return ret

    return wrapper


@logger
def add(x, y):
    return x + y


x = add(4, 5)
print(x)

logger 就是装饰器

add就是被包装函数

@logger就是装饰器语法

无参装饰器

​ 上例的装饰器语法,称为无参装饰器

​ @符号后是一个函数

​ 虽然是无参装饰器,但是@后的函数本质上是单参函数

​ 上例的logger函数是一个高阶函数

单参就是将未包装的add传给装饰器logger

上例@logger相当于add=logger(add)

计时装饰器

def test_time(fn1):
    def foo(x, y):
        import datetime
        star = datetime.datetime.now()
        fn1(x, y)
        end = datetime.datetime.now()
        return end - star

    return foo


def logger(fn):
    def wrapper(*args, **kwargs):
        print('调用前增强')
        ret = fn(*args, **kwargs)  # 参数解构
        print('调用后增强')
        return ret

    return wrapper


from time import sleep


@logger
@test_time
def add(x, y):
    sleep(2)
    return x + y


x = add(4, 5)
print(x)

带参装饰器

文档字符串

​ Python文档字符串Documentation Strings

​ 在函数(类、模块)语句块的第一行,且习惯是多行的文本,所以多使用三引号

​ 文档字符串也算是合法的一条语句

​ 惯例是首字母大写,第一行写概述,空一行,第三行写详细描述

​ 可以使用特殊属性__doc__访问这个文档

def add(x, y):
    """这是加法函数的文档"""
    return x + y


print("{}'s doc = {}".format(add.__name__, add.__doc__))
print(help(add))

#运行结果
add's doc = 这是加法函数的文档
Help on function add in module __main__:

add(x, y)
    这是加法函数的文档

None

或者在ipython中

def add1(x, y):
    """这是加法函数的文档"""
    return x + y

add1
Signature: add1(x, y)
Docstring: 这是加法函数的文档
File:      c:\users\administrator\desktop\学习笔记\python\代码\<ipython-input-6-757a8c9d3698>
Type:      function

装饰器的文档字符串问题

import time, datetime


def logger(fn):
    def wrapper(*args, **kwargs):
        """wrapper's doc~~~"""
        print('调用前增强')
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)  # 参数解构
        print('调用后增强')
        delta = (datetime.datetime.now() - start).total_seconds()
        print('Function {} took {}s.'.format(fn.__name__, delta))
        return ret

    return wrapper


@logger  # 等价于 add = wrapper <=> add = logger(add)
def add(x, y):
    """add's doc~~~"""
    time.sleep(2)
    return x + y


print(add.__name__, add.__doc__)
#结果
wrapper wrapper's doc~~~

显示的名字和文档却是替换后的

可以通过替换名字和文档来伪装称本函数

def copy_properties(src, dst):
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        
        
def logger(fn):
    def wrapper(*args, **kwargs):
        """wrapper's doc~~~"""
        print('调用前增强')
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)  # 参数解构
        print('调用后增强')
        delta = (datetime.datetime.now() - start).total_seconds()
        print('Function {} took {}s.'.format(fn.__name__, delta))
        return ret

    copy_properties(fn, wrapper)

    return wrapper

可以添加复制函数

def copy_properties(src, dst):
dst.__name__ = src.__name__
dst.__doc__ = src.__doc__

就可以达到目的

带参装饰器

可以将上例的函数改成装饰器

import time, datetime


#这个就相当与一个带单装饰器,带的参数是fn,也就是形参src,装饰的是logger中的wrapper,因为这里不需要实例化,所以没有带参数的第三层,而是直接返回了装饰的函数
def copy_properties(src):
    def foo(dst):
        """这里是foo函数"""
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        return dst
    	#这里装饰完了,就将源函数弹出,继续下面的流程

    return foo


def logger(fn):
    @copy_properties(fn)
    def wrapper(*args, **kwargs):
        """wrapper's doc~~~"""
        print('调用前增强')
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)  # 参数解构
        print('调用后增强')
        delta = (datetime.datetime.now() - start).total_seconds()
        print('Function {} took {}s.'.format(fn.__name__, delta))
        return ret

    return wrapper


@logger  # 等价于 add = wrapper <=> add = logger(add)
def add(x, y):
    """add's doc~~~"""
    time.sleep(2)
    return x + y


print(add.__name__, add.__doc__)

@copy_properties(fn)这种在装饰器后面跟着参数的装饰器称为带参装饰器

logger设定一个阈值,对执行时长超过阈值的记录一下

import time, datetime


def copy_properties(src):
    def foo(dst):
        """这里是foo函数"""
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        return dst

    return foo


def logger(duration):
    def foo1(fn):
        @copy_properties(fn)
        def wrapper(*args, **kwargs):
            """wrapper's doc~~~"""
            print('调用前增强')
            start = datetime.datetime.now()
            ret = fn(*args, **kwargs)  # 参数解构
            print('调用后增强')
            delta = (datetime.datetime.now() - start).total_seconds()
            print('slow') if delta > duration else print('fast')
            return ret

        return wrapper

    return foo1


@logger(1)
def add(x, y):
    """add's doc~~~"""
    time.sleep(2)
    return x + y


print(add(2, 3))

@copy_properties(fn)这种在装饰器后面跟着参数的装饰器称为带参装饰器。logger设定一个阈值,对执行时长超过阈值的记录一下进一步提取记录功能,因为有可能输出到控制台,也可能写入日志,这个由一个函数提供。

import time, datetime


def copy_properties(src):
    def foo(dst):
        """这里是foo函数"""
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        return dst

    return foo


def logger(duration,out=lambda name,time:print('{}执行了{}s,太慢了!'.format(name,time))):#这里加入了一个函数,超过了阈值,就会提醒
#这里的out就是一个函数需要两个参数,一个是name,一个是time
    def foo1(fn):
        @copy_properties(fn)
        def wrapper(*args, **kwargs):
            """wrapper's doc~~~"""
            print('调用前增强')
            start = datetime.datetime.now()
            ret = fn(*args, **kwargs)  # 参数解构
            print('调用后增强')
            delta = (datetime.datetime.now() - start).total_seconds()
            # print('slow') if delta > duration else print('fast')
            if delta>duration:
                #当超时,就调用这个out函数
                out(fn.__name__,delta)
            return ret

        return wrapper

    return foo1


@logger(8)
def add(x, y):
    """add's doc~~~"""
    time.sleep(2)
    return x + y


print(add(2, 3))

属性更新

上例中copy_properties是通用的功能,标准库中functools已经提供了

functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

需要引入函数update_wrapper

from functools import update_wrapper

​ 类似copy_properties功能

wrapper 包装函数、被更新者,wrapped 被包装函数、数据源

​ 元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性,有模块名__module__, 名称__name__, 限定名__qualname__, 文档__doc__, 参数注解__annotations__

​ 元组WRAPPER_UPDATES中是要被更新的属性,__dict__属性字典

​ 增加一个__wrapped__属性,保留着wrapped函数

import time, datetime
from functools import update_wrapper


# def copy_properties(src):
#     def foo(dst):
#         """这里是foo函数"""
#         dst.__name__ = src.__name__
#         dst.__doc__ = src.__doc__
#         return dst
# 
#     return foo


def logger(duration):
    def foo1(fn):
        # @copy_properties(fn)
        def wrapper(*args, **kwargs):
            """wrapper's doc~~~"""
            print('调用前增强')
            start = datetime.datetime.now()
            ret = fn(*args, **kwargs)  # 参数解构
            print('调用后增强')
            delta = (datetime.datetime.now() - start).total_seconds()
            return ret

        update_wrapper(wrapper, fn)#这个函数调用相当于17行的装饰器功能
        return wrapper

    return foo1


@logger(8)
def add(x, y):
    """add's doc~~~"""
    time.sleep(2)
    return x + y


print(add(2, 3))
print(add.__name__, add.__doc__)

functools模块提供了一个wraps装饰器函数,本质上调用的是update_wrapper,它就是一个属性复制函数

wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

需要导入函数

from functools import wraps

import time, datetime
from functools import update_wrapper,wraps


# def copy_properties(src):
#     def foo(dst):
#         """这里是foo函数"""
#         dst.__name__ = src.__name__
#         dst.__doc__ = src.__doc__
#         return dst
#
#     return foo


def logger(duration):
    def foo1(fn):
        # @copy_properties(fn)
        @wraps(fn)#放在被装饰函数上面,用法跟上一行自写装饰器相同
        def wrapper(*args, **kwargs):
            """wrapper's doc~~~"""
            print('调用前增强')
            start = datetime.datetime.now()
            ret = fn(*args, **kwargs)  # 参数解构
            print('调用后增强')
            delta = (datetime.datetime.now() - start).total_seconds()
            return ret

        # update_wrapper(wrapper, fn)
        return wrapper

    return foo1


@logger(8)
def add(x, y):
    """add's doc~~~"""
    time.sleep(2)
    return x + y


print(add(2, 3))
print(add.__name__, add.__doc__)

总结

​ @之后不是一个单独的标识符,是一个函数调用

​ 函数调用的返回值又是一个函数,此函数是一个无参装饰器

​ 带参装饰器,可以有任意个参数

​ @func()

​ @func(1)

​ @func(1, 2)

注解annotation

函数注解

def add(x:int, y:int) -> int:
    """

    :param x:int
    :param y:int
    :return:int
    """
    return x + y


print(add(1,2))

查询注解

print(add.__annotations__)
#运行结果
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}

类型注解

i: int = 3
j:str = 'abc'
k: str = 300
print(i, j, k)

inspect模块

inspect模块可以获取Python中各种对象信息。

inspect.isfunction(add),是否是函数

inspect.ismethod(pathlib.Path().absolute),是否是类的方法,要绑定

inspect.isgenerator(add)),是否是生成器对象

inspect.isgeneratorfunction(add)),是否是生成器函数

inspect.isclass(add)),是否是类

inspect.ismodule(inspect)),是否是模块

inspect.isbuiltin(print)),是否是内建对象

inspect.signature(callable, *, follow_wrapped=True)

获取可调用对象的签名

import inspect


def add(x: int, y: int) -> int:
    """

    :param x:int
    :param y:int
    :return:int
    """
    return x + y

print(add.__annotations__)
#结果
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}

a = inspect.signature(add)
print(a)
#结果
(x:int, y:int) -> int#获取的签名

inspect.Parameter

​ 4个属性

​ name,参数名,字符串

​ default,缺省值

​ annotation,类型注解

​ kind,类型

​ POSITIONAL_ONLY,只接受仅位置传参

​ POSITIONAL_OR_KEYWORD,可以接受关键字或者位置传参

​ VAR_POSITIONAL,可变位置参数,对应*args

​ KEYWORD_ONLY,对应*或者*args之后的出现的非可变关键字形参,只接受关键字传参

​ VAR_KEYWORD,可变关键字参数,对应**kwargs

​ empty,特殊类,用来标记default属性或者annotation属性为空

inspect.signature(fn).parameters['x'].default
#取出对应形参的缺省值

#判断缺省值为空
a.parameters['x'].default==inspect.Parameter.empty

#对注解的判断
a.parameters["x"].annotation==int

获取参数属性

1、先获取签名

a=inspect.signature(fn)
print(a)
#结果
(x:int, y:int) -> int

2、通过签名获取属性

b=a.parameters
print(b)
#结果
OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int">)])

#格式化成字典
c = dict(b)
print(c)
#结果
{'x': <Parameter "x:int">, 'y': <Parameter "y:int">}

#获得可用来比较的属性
print(c['x'].annotation)
#结果
<class 'int'>

写一个检查参数类型的装饰器

import inspect
from functools import wraps


def chack(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        # 先求位置参数的
        m = list(args)
        b = inspect.signature(fn).parameters
        c = dict(b)
        ve = list(b.values())
        for i, n in enumerate(m):
            if ve[i].annotation is not ve[i].empty and not isinstance(n, ve[i].annotation):#ve[i].empty表示默认值,这里为空
                print(ve[i].name, n, '属性是no!的')
        for k, v in kwargs.items():
            if c[k].annotation is not c[k].empty and not isinstance(v, c[k].annotation):
                print(k, v, '属性是no的')
        ff = fn(*args, **kwargs)#最后需要返回结果
        return ff

    return wrapper


@chack
def add(x: int, y: int) -> int:
    """

    :param x:int
    :param y:int
    :return:int
    """
    return x + y


print(add('b', y='a'))

functools模块

functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES) 用来更新函数属性 本身是个函数

wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES) 也是用来更新函数属性的,本身是个装饰器函数

reduce

functools.reduce(function, iterable[, initial])

就是减少的意思

初始值没有提供就在可迭代对象中取一个

求10的阶乘

print(reduce(lambda x, y: (x , y), range(1,11)))
#结果
(((((((((1, 2), 3), 4), 5), 6), 7), 8), 9), 10)

print(reduce(lambda x, y: x * y, range(1,11)))
#结果
3628800

partial

偏函数

​ 把函数部分参数固定下来,相当于为部分的参数添加了固定的默认值,形成一个新的函数,并返回这个新函数

​ 这个新函数是对原函数的封装

from functools import partial
import  inspect


def add(x, y):
    return x + y


newadd = partial(add, 11)
newadd1 = partial(add, x=11)
newadd2 = partial(add, y=11)
print(inspect.signature(newadd))
#结果
(y)

print(inspect.signature(newadd1))
#结果
(*, x=11, y)

print(inspect.signature(newadd2))
#结果
(x, *, y=11)

lru_cache

@functools.lru_cache(maxsize=128, typed=False)

​ lru即Least-recently-used,最近最少使用。cache缓存

​ 如果maxsize设置为None,则禁用LRU功能,并且缓存可以无限制增长。当maxsize是二的幂时,LRU功能执行得最好

​ 如果typed设置为True,则不同类型的函数参数将单独缓存。例如,f(3)和f(3.0)将被视为具有不同结果的不同调用

利用缓存,计算递归的斐波那契数列

from functools import lru_cache


@lru_cache()
def fib(i):
    if i==0:
        return 0
    elif i==1:
        return 1
    return fib(i-1)+fib(i-2)
print(fib(332))

运行结果
1082459262056433063877940200966638133809015267665311237542082678938909

标签:__,return,python,wrapper,add,print,装饰,def
From: https://www.cnblogs.com/guangdelw/p/16946070.html

相关文章

  • python 函数与生成器
    函数Python函数​ 由若干语句组成的语句块、函数名称、参数列表构成,它是组织代码的最小单元​ 完成一定的功能函数的作用​ 结构化编程对代码的最基本的封装,一般按......
  • python-练习(if for while语句)
    1.在终端中输入整数,打印正数,负数,零number=int(input("请输入整数"))ifnumber>0:print("正数")elifnumber<0:print("负数")else:print("零"......
  • Python 两个数字拼接
    问题:如何将两个数字拼接解决方法:将整形数字转成字符串拼接后,在转回整形。>>>a=1>>>b=2>>>c=str(a)+str(b)>>>print(int(c))12 ......
  • 第13章python实训
    实验报告实验目的1.了解和掌握Pygame的基础知识。【实验条件】1.PC机或者远程编程环境。 【实验内容】1.完成第十三章  实例01:篮球自动弹跳。 实例01:创建计......
  • PYTHON - openpyxl (二)
    1.1写数据语句说明工作表["a1"]=值写数据到一个单元格工作表.cell(行,列).value=值写数据到一个单元格工作表.cell(行,列,value=值)同上工作表.......
  • Python - 并行
    futures#使用方法一:defdisplay(*args):print(strftime('[%H:%M:%S]'),end='')print(*args)defloiter(n):msg='{}loiter({}):doingnothingf......
  • Python13章
    实验13:Pygame游戏编程一、实验目的和要求二、实验环境Python3.1064_bit三、实验过程1、实例1:制作一个跳跃的小球游戏(1)代码如下:1#-*-coding:utf-8-*-2......
  • securecrt9使用Python3
     很长一段时间,securecrt支持python,但版本为python2,而且自带的python解释器模块有缺失,关键是不支持三方库,使用上不方便。securecrt9.0开始支持Python3,不像python2.7......
  • 4-python的数据类型
    python为了应对不同的业务需求,也将数据分成了不同的类型1-numbersint(有符号整型)long(长整型可以代表八进制和16进制)float(浮点型)comple......
  • python中的公共操作与列表推导式
    1.公共操作#+合并将两个相同类型序列进行连接字符串、列表、元组l1=[1,2,3]l2=[4,5,6]print(l1+l2)#[1,2,3,4,5,6]  #*复制将里面的......