对Python【装饰器】的一点思考

会有一股神秘感。 提交于 2020-02-11 01:28:59

装饰器

python中装饰器做的事情:它们封装一个函数,并且用这样或者那样的方式来修改它的行为,装饰器让你在一个函数的前后去执行代码。在代码运行期间动态增加功能。
本质上:decorator(装饰器)就是一个返回函数的高阶函数

一、装饰器基础:

一切皆可对象,函数也是个对象,函数对象被赋值给变量,也可从函数中返回函数,甚至将函数作为参数传给另一个函数:

def now():
    print('2020.2.10')
f=now     # 一切皆可对象,函数也是个对象,函数对象被赋值给变量
f()       # 调用该函数

利用__name__属性,可以拿到函数的名字:

print(now.__name__)   
print(f.__name__)    # __name__属性,拿到函数的名字
now

二、装饰器的蓝本:

from functools import wraps      # 装饰器会重写被装饰函数的名字和注释文档(docstring),有些依赖函数签名的代码执行就会出错,当用__name__属性查看函数名字会变成装饰器中的函数名。可用functools.wraps解决这一问题
def decorator_name(f):   # 装饰器,它的参数为function即函数
    @wraps(f)        # 用@来包裹被修饰的函数
    def decorated(*args, **kwargs):  # 参数定义是(*args, **kw),因此,decorated()函数可以接受任意参数的调用
        if not can_run:
            return "Function will not run"  # return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
        return f(*args, **kwargs)   # 返回调用被修饰函数
    return decorated
 
@decorator_name   # 借助Python的@语法,把decorator置于函数的定义处
def func():     # 此处func()为被修饰函数
    return("Function is running")
 
can_run = True
print(func())
# Output: Function is running
 
can_run = False
print(func())
# Output: Function will not run

print(func.__name__)
# Output:func     __name__属性,拿到函数的名字.由于functools.wraps的帮助名字无问题

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数:

import functools   # 导入functools模块

def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

@log('execute')
def now():
    print('2020-2-10')

>>> now()
execute now():
2020-2-10

三、类装饰器:

相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点

使用类装饰器主要依靠类的__call__方法,当使用@形式将装饰器附加到函数上时,就会调用此方法:

from functools import wraps
 
class logit(object):
    def __init__(self, logfile='out.log'):
        self.logfile = logfile
 
    def __call__(self, func):
        @wraps(func)   # 语法糖@
        def wrapped_function(*args, **kwargs):
            log_string = func.__name__ + " was called"
            print(log_string)
            # 打开logfile并写入
            with open(self.logfile, 'a') as opened_file:
                # 现在将日志打到指定的文件
                opened_file.write(log_string + '\n')
            # 现在,发送一个通知
            self.notify()
            return func(*args, **kwargs)
        return wrapped_function
 
    def notify(self):
        # logit只打日志,不做别的
        pass

@logit()  # 语法糖@
def myfunc1():
    pass

四、装饰器顺序:

一个函数还可以同时定义多个装饰器,比如:

@a
@b
@c
def f ():
    pass

它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于:

f = a(b(c(f)))

五、Task1:

设计一个decorator, 可作用于任何函数, 并打印该函数执行时间:

from functools import wraps
import time

# 设计一个decorator, 可作用于任何函数, 并打印该函数执行时间

def metric(func):
    @wraps(func)
    def wrapper(*args,**kw):
        start=time.time()
        func(*args,**kw)
        end=time.time()
        print('%s executed in %.4f ms'%(func.__name__,end-start))  # %.f表示浮点型输出,%.af表示输出保留小数点后a位并且考虑四舍五入,(如%.2f保留后2位,%.f保留0位(i.e不保留))
        return func(*args,**kw)
    return wrapper 

# 测试
@metric
def fast(x, y):
    time.sleep(0.0012)
    return x + y;

@metric
def slow(x, y, z):
    time.sleep(0.1234)
    return x * y * z;

f = fast(11, 22)
s = slow(11, 22, 33)
if f != 33:
    print('测试失败!')
elif s != 7986:
    print('测试失败!')

六、Task2:

设计一个decorator,能在函数调用的前后打印出’begin call’和’end call’的日志,同时支持带参和不带参的函数运行:

from functools import wraps

def log(func1):    # 此为高阶函数
    if isinstance(func1,str):   # 若高阶函数传入的参数是字符串,则可用isinstance判断
        def decorator(func2):   # 声明一个装饰器函数
            @wraps(func2)
            def wrapper(*args,**kw):
                print('%s begin call %s'%(func1,func2.__name__))
                func2(*args,**kw)
                print('%s end call %s'%(func1,func2.__name__))
                return func2(*args,**kw)
            return wrapper
        return decorator   # 第一种情况,这是一个返回decorator(装饰器)的高阶函数
    else:
        @wraps(func1)
        def wrapper(*args,**kw):  # 第二种情况,无需声明再装饰器函数,该高阶函数即为装饰器函数
            print('begin call %s'%func1.__name__)
            func1(*args,**kw)
            print('end call %s'%func1.__name__)
            return func1(*args,**kw)
        return wrapper


@log
def f():
    pass

f()
# Output:
begin call f
end call f





@log('execute')    # 先给log函数传入字符串类型的参数'execute',再给它传入函数类型的参数f()
def x():
    pass

x()
# Output:
execute begin call x
execute end call x

参考网站(菜鸟教程)

参考网站(廖雪峰官方网站)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!