文章目录
装饰器的作用
- 打印日志: @log
- 检测性能:@performance
- 数据库事务: @transaction
- URL路由:@post(’/register’)
装饰器的目的:
- 使代码可读性更高,感觉高大上;
- 代码结构更加清晰,代码冗余度更低;
无参数decorator
Python的 decorator 本质上就是一个高阶函数,它接收一个函数作为参数,然后,返回一个新函数。
使用 decorator 用Python提供的 @ 语法,这样可以避免手动编写f = decorate(f) 这样的代码。
考察一个@log的定义:
def testlog(func):
def inner(arg):
print("print log:" + func.__name__)
func(arg)
return inner
# 不使用@
def test(x):
print("this is test function:",x)
test = testlog(test)
test("11")
# 使用@
@testlog
def test(x):
print("this is test function:",x)
test("11")
# print log:test
# this is test function:11
例子:阶乘
from functools import reduce
def testlog(func):
def inner(*args,**kwargs):
# 要让 @testlog 自适应任何参数定义的函数,可以利用Python的 *args 和 **kw,保证任意个数的参数总是能正常调用
print("print log:" + func.__name__)
func(*args,**kwargs)
return inner
@testlog
def factorial(x, y):
print("this is factorial function:")
res = reduce(lambda x, y: x * y, range(x, y))
print(res)
return res
factorial(1, 11)
# print log:factorial
# this is factorial function:
# 3628800
例子:写一个@performance,作为一个函数计时器。
import time
def performance(func):
def inner(*args, **kwargs):
time1 = time.time()
res = func(*args, **kwargs)
time2 = time.time()
print(f"call %s%s%s in %fs" % (func.__name__, args,kwargs, time2 - time1))
return res
return inner
@performance
def factorial(x, y):
return reduce(lambda x, y: x * y, range(x, y))
print(factorial(1, 11))
# call factorial(1, 11){} in 0.000999s
# 3628800
有参数decorator
在之前没有带参数的装饰器上添加参数:
from functools import reduce
import time
def performance(unit):
def decorator_(func):
def wrap(*args, **kwargs):
time1 = time.time()
res = func(*args, **kwargs)
time.sleep(1)
time2 = time.time()
t = 1000 * (time2 - time1) if unit == 'ms' else (time2 - time1)
print(f"call %s in %s %s"%(func.__name__, t, unit))
return res
return wrap
return decorator_
@performance('ms')
def factorial_(n):
return reduce(lambda x,y: x*y, range(1, n+1))
print(factorial_(6))
# call factorial_ in 1000.087261199951 ms
# 720
装饰器的优化
@decorator可以动态实现函数功能的增加,但是,经过@decorator“改造”后的函数,和原函数相比,除了功能多一点外,还有其它不同的地方。
在没有decorator的情况下,打印函数名:
def f1(x):
pass
print(f1.__name__) # f1
有decorator的情况下,再打印函数名:
def log(func):
def wrapper(*args, **kw):
return func(*args, **kw)
return wrapper
@log
def f2(x):
pass
print(f2.__name__) # wrapper
由于decorator返回的新函数函数名已经不是’f2’,而是@log内部定义的’wrapper’。这对于那些依赖函数名的代码就会失效。decorator还改变了函数的__doc__等其它属性。如果要让调用者看不出一个函数经过了@decorator的“改造”,就需要把原函数的一些属性复制到新函数中:
def log(func):
def wrapper(*args, **kw):
return func(*args, **kw)
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
@log
def f2(x):
pass
print(f2.__name__) # f2
这样写decorator很不方便,也很难把原函数的所有必要属性都一个一个复制到新函数上,所以Python内置的functools可以用来自动化完成这个“复制”的任务:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
return func(*args, **kw)
return wrapper
@log
def f2(x):
pass
print(f2.__name__) # f2
# 带参数的装饰器
# 注意@functools.wraps应该作用在返回的新函数上。
def performance(prefix):
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
return func(*args, **kw)
return wrapper
return log
@performance("debug")
def f2(x):
pass
print(f2.__name__) # f2
类装饰器(不带参数)
基于类装饰器的实现,必须实现 __call__
和 __init__
两个内置函数。
__init__
:接收被装饰函数
__call__
:实现装饰逻辑。
class logger(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print(f"logger:{self.func.__name__} in decorator")
return self.func(*args, **kwargs)
@logger
def test(para):
print(f"test func({para})")
test("123")
# logger:test in decorator
# test func(123)
类装饰器(带参数)
若还需要打印DEBUG
WARNING
等级别的日志。这就需要给类装饰器传入参数,给这个函数指定级别了。
带参数和不带参数的类装饰器有很大的不同。
__init__
:不再接收被装饰函数,而是接收传入参数。
__call__
:接收被装饰函数,实现装饰逻辑。
class logger(object):
def __init__(self, level='info'):
self.level = level
def __call__(self, func):
def wrapper(*args, **kwargs):
print(f"logger[{self.level}]:{func.__name__} in decorator")
func(*args, **kwargs)
return wrapper
@logger(level='warning')
def test_funct(para):
print(f'test test_funct({para})')
test_funct("321")
# logger[warning]:test_funct in decorator
# test test_funct(321)
偏函数
当一个函数有很多参数时,调用者就需要提供多个参数。如果减少参数个数,就可以简化写代码的负担。
比如,int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:
>>> int('12345')
12345
但int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做 N 进制的转换:
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565
假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:
def int2(x, base=2):
return int(x, base)
这样,我们转换二进制就非常方便了:
>>> int2('1000000')
64
>>> int2('1010101')
85
functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2("11",base=2) # 临时需要可以更改
3
所以,functools.partial可以把一个多参数的函数变成一个参数少的新函数,少的参数需要在创建时指定默认值,这样,新函数调用的难度就降低了。
偏函数与类实现装饰器
大多数装饰器都是基于函数和闭包实现的,但这并非构造装饰器的唯一方式。
Python 对某个对象是否能通过装饰器( @decorator
)形式使用只有一个要求:decorator 必须是一个“可被调用(callable)的对象。
对于callable 对象,最熟悉的就是函数了;
除函数之外,类也可以是 callable 对象,只要实现了__call__
函数(上面几个例子)。
还有容易被人忽略的偏函数其实也是 callable 对象。
import time
import functools
class DelayFunc:
def __init__(self, duration, func):
self.duration = duration
self.func = func
def __call__(self, *args, **kwargs):
print(f'Wait for {self.duration} seconds...')
time.sleep(self.duration)
return self.func(*args, **kwargs)
def eager_call(self, *args, **kwargs):
print('Call without delay')
return self.func(*args, **kwargs)
def delay(duration):
"""
装饰器:推迟某个函数的执行。
同时提供 .eager_call 方法立即执行
"""
# 此处为了避免定义额外函数,
# 直接使用 functools.partial 帮助构造 DelayFunc 实例
return functools.partial(DelayFunc, duration)
@delay(duration=2)
def add(a, b):
return a+b
print(add) # add 变成了类DelayFunc的实例
# <__main__.DelayFunc object at 0x000001EED91FA3C8>
print(add(3, 5)) # 直接调用实例,进入 __call__
# Wait for 2 seconds...
# 8
print(add.eager_call(1,2)) # 实现实例方法
# Call without delay
# 3
装饰类的装饰器
用 Python 写单例模式的时候,常用的有三种写法。其中一种,是用装饰器来实现的。
instances = {}
def singleton(cls):
def get_instance(*args, **kwargs):
cls_name = cls.__name__
print("cls_name:",cls_name)
if not cls_name in instances:
print(f"cle_name[{cls_name}] not in instances")
instance = cls(*args, **kwargs)
instances[cls_name] = instance
return instances[cls_name]
return get_instance
@singleton
class User:
_instance = None
def __init__(self, name):
self.name = name
print("name=",self.name)
u1 =User("usr1")
print(u1,u1.name)
u2 =User("usr2")
print(u2,u2.name)
print(u1 == u2)
# cls_name: User
# cle_name[User] not in instances
# name= usr1
# <__main__.User object at 0x000001B2D33BB2B0> usr1
# cls_name: User
# <__main__.User object at 0x000001B2D33BB2B0> usr1
# True
wraps再理解
了解完偏函数后再回看functools.wraps()的作用
wraps 其实是一个偏函数对象(partial),源码如下:
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
wraps其实就是调用了一个函数update_wrapper
,知道原理后,我们改写上面的代码,在不使用 wraps的情况下,也可以让wrapped.__name__
打印出 wrapped,代码如下:
from functools import update_wrapper
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
'__annotations__')
def decorator(func):
def inner():
pass
update_wrapper(inner, func, assigned=WRAPPER_ASSIGNMENTS)
return inner
@decorator
def testfunc():
pass
print(testfunc.__name__) # testfunc
一般最常使用的就是:
from functools import wraps
def decorator(func):
@wraps(func)
def inner():
pass
return inner
@decorator
def testfunc():
pass
print(testfunc.__name__) # testfunc
python内置装饰器:property
property通常存在于类中,可以将一个函数定义成一个属性,属性的值就是该函数return的内容。
初学Python时这样给实例绑定属性:
class Person(object):
def __init__(self, name, age= None):
self.name = name
self.age = age
# instantiate
p1 = Person("person1")
# add attribute
p1.age = 25
# query attrbute
print(p1.name)
# delete attribute
del p1.age
但是这样存在问题:直接把属性暴露出去,虽然写起来很简单,但是并不能对属性的值做合法性限制。合理的写法如下:
class Person(object):
def __init__(self, name, age= None):
self.name = name
self.__age = age
def set_age(self, age):
if not isinstance(age, int):
raise ValueError('input illegal: age must be int.')
if not 0 < age < 100:
raise ValueError('input illegal: age must between 0 and 150.')
self.__age = age
def get_age(self):
return self.__age
def del_age(self):
self.__age = None
# instantiate
p1 = Person("tiny")
# add attribute
p1.set_age(25)
# query attrbute
print(p1.get_age())
# delete attribute
p1.del_age()
上面的代码设计虽然可以变量的定义,但是可以发现不管是获取还是赋值(通过函数)都和我们平时想要的的不同。我们想要的是这样形式的:赋值p1.age = 25
, 获取p1.age
。
使用@property :
class Person(object):
def __init__(self, name, age=None):
self.name = name
self.__age = age
@property
def age(self):
return self.__age
@age.setter
def age(self, value):
if not isinstance(value, int):
raise ValueError('age must be int.')
if not 0 < value < 150:
raise ValueError('age must between 1 and 150.')
self.__age = value
@age.deleter
def age(self):
del self.__age
# instantiate
p1 = Person("tiny")
# modify attribute
p1.age = 25
# query attrbute
print(p1.age)
# delete attribute
del p1.age
用@property
装饰过的函数,会将一个函数定义成一个属性,属性的值就是该函数return的内容。同时,会将这个函数变成另外一个装饰器。就像@age.setter
和@age.deleter
。
@age.setter
使得我们可以使用p1.age = 25
这样的方式直接赋值;
@age.deleter
使得我们可以使用del p1.age
这样的方式来删除属性。
参考来源:
慕课网python进阶教程
微信公号:Python学习开发