python装饰器常见问题_python 实用编程技巧 —— 装饰器使用问题与技巧

如何使用函数装饰器

常用的斐波那契的写法

def fibonacci(n):

if n<=1:

return 1

return fibonacci(n-1)+fibonacci(n-2)

这种方法也是C语言常用的递归算法。会进行大量的重复计算。如计算(10)时需要计算(8)和(9),计算(9)时需要计算(7)和(8)

改写传统的斐波那契

def fibonacci(n,cache=None):

#cache(n) #计算开始时先去缓存里找是否计算过n,如存在直接返回,否则计算,并把计算结果放在缓存中。

if cache is None: #创建一个空的字典

cache = {}

if n in cache:

return cache[n]

if n<=1:

return 1

cache[n] = fibonacci(n-1,cache) + fibonacci(n-2,cache)

return cache[n]

if __name__ == '__main__':

print(fibonacci(50))

可以发现瞬间计算出数值

使用装饰器

def memo(func):

cache = {}

def wrap(n):

if n not in cache:

cache[n] = func(n)

return cache[n]

return wrap

@memo

def fibonacci(n):

if n <= 1:

return 1

return fibonacci(n - 1) + fibonacci(n - 2)

if __name__ == '__main__':

print(fibonacci(50))

如何为被装饰的函数保存元数据

在函数对象中保存着一些函数的元数据, 例如:

f.__name__: 函数的名字

f.__doc__: 函数文档字符串

f.__module__: 函数所属模块名

f.__dict__: 属性字典

f.__defaults__: 默认参数元组

我们在使用装饰器后, 再访问上面这些属性访问时,

看到的是内部包裹函数的元数组, 原来函数的元数据便丢失掉了, 应该如何解决?

解决方案

使用update_wrapper

使用wraps

from functools import wraps

def mydecorator(func): #装饰器,本例没增加功能,只做为演示

@wraps(func)

def wrapper(*args,**kargs):

"""wrapper function"""

print ('In wrapper')

func(*args,**kargs)

# update_wrapper(wrap, func)

return wrapper

@mydecorator

def example():

"""example function"""

print ('In example')

if __name__ == '__main__':

example()

print(example.__name__)

如何定义带参数的装饰器

实际案例

实现一个装饰器, 它用来检查被装饰函数的参数类型。

装饰器可以态可以通过参数指明函数参数的类型, 调用时如果检测出类型不匹配则抛出异常。

@type_assert(str, int, int)

def f(a, b, c):

...

@type_assert(y=list)

def g(x, y):

.解决方案

提取函数签名: inspect.signature()

带参数的装饰器, 也就是根据参数定制化一个装饰器, 可以看成生产装饰器的工厂。 每次调用type_assert, 返回一个特定的装饰器,然后用它去修饰其他函数

import inspect

def type_assert(*ty_args, **ty_kwargs): # 带参数的装饰器函数, 要增加一层包裹 参数是 装饰器的参数

def decorator(func):

# inspect.signature(func) 函数观察对象, 方便后面获取 参数-类型 字典与参数-值字典

func_sig = inspect.signature(func) # (a,b,c)

# 将装饰器参数 组成参数-类型 字典 如 {a:int, b:str}

bind_type = func_sig.bind_partial(*ty_args, **ty_kwargs).arguments # OrderedDict([('c', )])

# func_sig.bind_partial 绑定部分参数可以得到 参数类型字典,

# 比如 参数是a=1, b='bbbb', c=2 装饰器参数是 a=int, b=str ,则得到{'a':int, 'b':str}

# 如果使用 func_sig.bind 则装饰器参数中 不能缺少 c 的类型

def wrap(*args, **kwargs): # 参数是func的 参数

for name, obj in func_sig.bind(*args, **kwargs).arguments.items(): # 得到 参数-值 字典

type_ = bind_type.get(name) # 从 参数-类型 字典中 得到 参数 应该属于的 类型

if type_:

if not isinstance(obj, type_):

raise TypeError('%s must be %s' % (name, type_))

return func(*args, **kwargs)

return wrap

return decorator

@type_assert(c=str)

def f(a, b, c):

pass

if __name__ == '__main__':

f(5, 10, 's') # 校验通过

f(5, 10, 1) # 检验失败 1 不是字符串类型

如何实现属性可修改的函数装饰器

实际案例

在某项目中, 程序运行效率差, 为分析程序内哪些函数执行时间开销大, 我们实现一个带timeout参数的函数装饰器。 装饰功能如下:

1.统计被装饰函数单次调用运行时间

2.时间大于参数 timeout的, 将此次函数调用记录到log 日志中

3.运行时可修改 timeout 的值

@warn_timeout(1.5)

def func(a, b):

...

解决方案

为包裹函数添加一个函数, 用来修改闭包中使用的自由变量。 在python3中:使用nonlocal 来访问潜逃作用域中的变量引用

import time

import logging

def warn_timeout(timeout):

def decorator(func):

# _timeout = [timeout]

def wrap(*args, **kwargs):

# timeout = _timeout[0]

t0 = time.time()

res = func(*args, **kwargs)

used = time.time() - t0

if used > timeout:

logging.warning('%s: %s > %s', func.__name__, used, timeout) # logging.warning 打印 输出到控制台

return res

def set_timeout(new_timeout):

nonlocal timeout # timeout 是闭包 变量

timeout = new_timeout

# _timeout[0] = new_timeout

wrap.set_timeout = set_timeout # 使timeout 可修改

return wrap

return decorator

import random

@warn_timeout(1.5)

def f(i):

print('in f [%s]' % i)

while random.randint(0, 1):

time.sleep(0.6)

for i in range(3):

f(i)

f.set_timeout(1) # 修改timeout 参数 从1.5 变为1

for i in range(3):

f(i)

输出结果如下

5cc567d1f7c84aa87ca3784384aed960.png

如何在类中定义装饰器

实际案例

实现一个能将函数调用信息记录到日志的装饰器:

把每次函数的调用时间, 执行时间, 调用次数写入日志

可以对被装饰函数分组, 调用信息记录到不同日志

动态修改参数, 比如日志格式

动态打开关闭日志输出功能

解决方案

为了让装饰器在使用上更加灵活, 可以把类的实例方法作为装饰器,此时在包裹函数中就可以持有实例对象, 便于修改属性和拓展功能

import time

import logging

DEFAULT_FORMAT = '%(func_name)s -> %(call_time)s\t%(used_time)s\t%(call_n)s'

class CallInfo:

def __init__(self, log_path, format_=DEFAULT_FORMAT, on_off=True):

self.log = logging.getLogger(log_path)

self.log.addHandler(logging.FileHandler(log_path))

# 这样可以通过log 往 log_path 输出信息

self.log.setLevel(logging.INFO) # 设置log级别

self.format = format_

self.is_on = on_off

# 装饰器方法

def info(self, func):

_call_n = 0 # 被调用次数

def wrap(*args, **kwargs):

func_name = func.__name__

call_time = time.strftime('%x %X', time.localtime())

# localtime 格式化时间戳为本地的时间 strftime 则得到时间字符串

# % x

# 本地相应的日期表示

# % X

# 本地相应的时间表示

t0 = time.time()

res = func(*args, **kwargs)

used_time = time.time() - t0

nonlocal _call_n

_call_n += 1

call_n = _call_n

if self.is_on:

self.log.info(self.format % locals()) # locals 即wrap函数中的变量 对应的字典

return res

return wrap

def set_format(self, format_):

self.format = format_

def turn_on_off(self, on_off):

self.is_on = on_off

# 测试代码

import random

ci1 = CallInfo('mylog1.log')

ci2 = CallInfo('mylog2.log')

@ci1.info

def f():

sleep_time = random.randint(0, 6) * 0.1

time.sleep(sleep_time)

@ci1.info

def g():

sleep_time = random.randint(0, 8) * 0.1

time.sleep(sleep_time)

@ci2.info

def h():

sleep_time = random.randint(0, 7) * 0.1

time.sleep(sleep_time)

for _ in range(3):

random.choice([f, g, h])()

ci1.set_format('%(func_name)s -> %(call_time)s\t%(call_n)s') # 去掉使用时间

for _ in range(3):

random.choice([f, g])()

2f71c723926d176f14b892f12e18cccd.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值