python

python笔记11:装饰器

2022-04-19  本文已影响0人  _百草_

1. 什么是装饰器

函数也是一个对象,并且可以赋值给变量==>通过变量调用该函数

def main():
    demo_func()


if __name__ == "__main__":
    a = main  # 函数对象赋值给变量;这里没有使用小括号,因为我们并不是在调用main函数
    a()  # 通过变量调用该函数
    print(a.__name__)  # 获取函数名 输出:main
    print(main.__name__)  # 获取函数名 输出:main
    
    # 删除对象
    del main
    a()  # 再次调用,正常
    main()  # 再次调用报错;NameError: name 'main' is not defined

期望:增强main()函数功能,如执行前后打印log,但不修改main()函数定义
==>在代码运行期间增加功能的方式,称之为“装饰器”
本质上,装饰器decorator是一个返回函数的高阶函数
即:改变其他函数功能的函数
通过装饰器函数,来增强原函数的一些功能,且原函数不需要修改

1.1 在函数中定义函数

def main():
    print("这是main函数!")
    def hello():
        print("这是Hello函数!")
    def world():
        print("这是World函数!")
    return hello(), world()  # 调用函数内函数

main()  # 调用main(),函数内函数hello()和world()将同时调用
hello()  # 函数main()外,无法直接访问函数内函数hello(),报错NameError: name 'hello' is not defined

1.2 从函数中返回函数

def main(name=""):
    def welcome():
        return "welcome"

    def other():
        return name

    if name:
        return other  # 不需要在一个函数中执行另一个函数
    else:
        return welcome


a = main("hello")  # 若不加小括号则是赋值,不是调用
print(a)  # 输出<function main.<locals>.other at 0x000001271DDAE1F0>
print(a())  # a 指向函数,返回加小括号调用该函数

1.3 将函数作为参数传给另一函数

def main():
    return "这是main函数!"

def before(func):  # 一般func代指函数
    print("执行前调用")
    print(func())  # 函数调用

before(main)  # 注意传参是函数,此处不用小括号

1.4 第一个装饰器

# 1---------------------
# # 没有返回函数,报错:TypeError: 'NoneType' object is not callable
 def print_log(func):
     print(f"开始执行{func.__name__}!")
     func()
     print(f"结束执行{func.__name__}!")
# 1---------------------


# 2--------------------
# 问题:函数func重复执行
 def print_log(func):
    print(f"开始执行{func.__name__}!")
     func()
     print(f"结束执行{func.__name__}!")
     return func
# 2--------------------

# 3 使用函数内函数添加功能,函数返回该内部函数即可
def print_log(func): # 接收一个函数作为参数
    def wrapper():
        print(f"开始执行{func.__name__}!")
        func()
        print(f"结束执行{func.__name__}!")
    return wrapper  # 返回一个函数


# 方式1:使用装饰器的函数上方添加@func_name
@print_log
def main():
    print("正在执行main...")


main()

# # 方式2:直接调用
# a = print_log(main)
# a()

装饰器调用:
@称之为语法糖,相当于a = print_log(main)

@log  # 使用@decorator_name ,放置在函数定义前;
# 注意:使用的装饰器(函数),后面不需要添加小括号
def main():
    print("main")

在面向对象OOP的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现。

  1. 支持OOP的decorator
  2. 从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。

2. 简单的装饰器

装饰器常用场景:日志、授权
通常情况下,会把*args**kwargs,作为装饰器内部函数wrapper()的参数=>表示接受任意数量和类型的参数。

import functools


# 装饰器
def decorator_name(func):
    @functools.wraps(func)
    def decorated(*args, **kwargs):
        if not can_run:
            return "function will not run"
        return func(*args, **kwargs)
    return decorated


@decorator_name
def funct(name):
    return f"Function is running!-name={name}"


can_run = True
print(funct("wlh"))   # 输出:Function is running!-name=wlh
can_run = False
print(funct("百草"))  # 输出:function will not run

3. 带参数的装饰器

from functools import wraps


def log_var(filename):  # 创建一个包裹函数
    def print_log(func):  # 简单装饰器
        @wraps(func)
        def wrapper(*args, **kwargs):  # 为另一函数附加功能的函数
            print("开始执行")
            with open(filename, "a", encoding="utf-8") as f:
                f.write("开始执行!")
            func(*args, **kwargs)
            print("结束执行")
            with open(filename, "a", encoding="utf-8") as f:
                f.write("结束执行!")

        return wrapper
    return print_log  # 返回简单装饰器

4. 类装饰器

类也可以用来构建装饰器
类装饰器,主要依赖于函数__call__,每当调用一个实例时,函数__call__就会被执行一次。

from functools import wraps


class Log:
    """
    比方说有时你只想打日志到一个文件。
    而有时你想把引起你注意的问题发送到一个email,同时也保留日志,留个记录。
    这是一个使用继承的场景,但目前为止我们只看到过用来构建装饰器的函数。
    """
    def __init__(self, filename="out.log"):
        self.filename = filename

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print("开始执行!")
            print(self.filename)
            with open(self.filename, 'a', encoding="utf-8") as f:
                # 打印日志
                f.write("开始执行!\n")
            self.notify()  # 发送通知
            return func(*args, **kwargs)
        return wrapper

    def notify(self):
        """发送通知"""
        print("发送通知")
        pass


# 继承,创建子类
class EmailLog(Log):
    """一个logit的实现版本,可以在函数调用时发送email给管理员"""

    def __init__(self, email, *args, **kwargs):
        self.email = email
        super(EmailLog, self).__init__(*args, **kwargs)  # 继承类

    def notify(self):
        # 发送邮件到指定邮箱
        print("发送邮件到指定邮箱")


# @Log()  # 调用修饰器,注意类装饰器使用时,需要小括号
# def main():
#     print("执行main")
#
#
# main()


@EmailLog("123")
def two():
    print("执行two")

two()

5. 装饰器嵌套


6. @functools.wrap装饰器使用

print(main.__name__)  # 输出结果:wrapper;即重写了函数的名字和注释文档docstring
print(main.__doc__)  # 输出结果:这是wrapper的docstring

"""
使用functools.wraps来解决函数名和注释文档的重写
"""
from functools import wraps


def new_decorator(func):
    """这是new_decorator的docstring"""
    @wraps(func)  # 在定义的wrapper上添加休修饰器即可
    def wrapper():
        """这是wrapper的docstring"""
        print("执行前!")
        func()
        print("执行后!")
    return wrapper


@new_decorator
def requiring_decorator():
    """这是requiring_decorator的docstring"""
    print("这是requiring decorator函数!")


print(requiring_decorator.__name__)
print(requiring_decorator.__doc__)


注:

  1. python3-装饰器基础使用
  2. Learn python 3:装饰器
  3. Python 函数装饰器
  4. 装饰器-廖雪峰的官方网站
上一篇下一篇

猜你喜欢

热点阅读