python:装饰器

一、函数装饰器

  1.装饰器本身不带参数:采用两层函数定义装饰器(外层函数起到传入被修饰函数的效果,里层函数起到在被修饰函数前后增加功能的效果)
def print_func_name(func):
    '''
        定义装饰器:打印函数名称
    '''
    def wrapper(*args,**kwargs):
        print("装饰器进入")
        print(f'装饰器功能显示使用装饰器函数的函数名:{func.__name__}')
        #这里可以把传入函数的调用结果存入到变量
        #等函数后加入的代码执行后再把结果返回
        func_res=func(*args,**kwargs)
        print("装饰器结束")
        return func_res
    return wrapper

@print_func_name
def hellomm(str):
    print(f'我是{str}')
    str="函数属性"
    return str

print(hellomm('函数本尊'))
#结果:
装饰器进入
装饰器功能显示使用装饰器函数的函数名:hellomm
我是函数本尊
装饰器结束
函数属性
  2.装饰器本身自带传入参数的装饰器:采用三层函数定义装饰器(在不带参装饰器外层嵌套函数起到一个传入参数的效果)
def print_before_func(str):
    def decorator(func):
        def wrapper(*args,**kwargs):
            print(f'装饰器:我是{str}')
            return func(*args,**kwargs)
        return wrapper
    return decorator

@print_before_func('装饰器')
def printname(str):
    print(f'函数:我是{str}')

printname('函数本身')
#结果:
装饰器:我是装饰器
函数:我是函数本身
  ddt中的装饰器写法片段:
FILE_ATTR = '%file_path'           # store the path to JSON file
YAML_LOADER_ATTR = '%yaml_loader'  # store custom yaml loader for serialization
def file_data(value, yaml_loader=None):
    def wrapper(func):
        setattr(func, FILE_ATTR, value)
        if yaml_loader:
            setattr(func, YAML_LOADER_ATTR, yaml_loader)
        return func
    return wrapper
  3.wraps装饰器的使用

使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、name、参数列表,看例子:

#查看f函数的名称和文档
def f(x):
    """does some math"""
    return x + x * x
print(f.__name__)
print(f.__doc__)
#结果:
f
does some math
#查看使用装饰器后f函数的名称和文档
def logged(func):
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging
@logged
def f(x):
    """does some math"""
    return x + x * x
print(f.__name__)
print(f.__doc__)
#结果:
with_logging
None

不难发现,f函数名称变成with_logging了,f函数的文档注释docstring也找不到了。
这个问题就比较严重的,好在我们有wraps装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。

from functools import wraps
def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(f"被修饰的函数:{func.__name__}")
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
    """does some math"""
    return x + x * x
print(f"函数名:{f.__name__}")  # prints 'f'
print(f"函数注释文档:{f.__doc__}")  # prints 'does some math'
print(f(5))
#结果:
函数名:f
函数注释文档:does some math
被修饰的函数:f
30

二、类装饰器

  1.类装饰器添加或修改类的属性内容
def Decorator(obj):
    '''
      实现一个类装饰器
    '''
    #添加属性
    obj.age = "18岁"#给类添加一个age属性
    obj.gender="男"#给类添加一个gender属性
    def eat():
        #给类添加一个eat函数
        print("正在吃苹果")
        return "苹果吃完了"
    obj.eat = eat#使用函数名添加一个函数属性
    return obj#返回对象

@Decorator          #相当于执行 School = Decorator(School)
class Human():
    def __init__(self, name, gender):
        self.name =name
        self.gender =gender
#打印类的属性字典
print(f"Human类的属性:{Human.__dict__}")
human=Human("倩倩","女")
print(f"调用Human类中的性别属性:{Human.gender}")
print(f"human实例的属性:{human.__dict__}")
print(f"调用human实例的属性:{human.name,human.gender,human.age}")
print(f"访问Human的eat函数:{Human.eat()}")
print(f"访问human的eat函数:{human.eat}")
#结果:
Human类的属性:{'__module__': '__main__', '__init__': <function Human.__init__ at 0x000001C4CEADB670>,
 '__dict__': <attribute '__dict__' of 'Human' objects>, '__weakref__': <attribute '__weakref__' of 'Human' objects>, 
'__doc__': None, 'age': '18岁', 'gender': '男', 'eat': <function Decorator.<locals>.eat at 0x000001C4CEADBCA0>}
调用Human类中的性别属性:男
human实例的属性:{'name': '倩倩', 'gender': '女'}
调用human实例的属性:('倩倩', '女', '18岁')
正在吃苹果
访问Human的eat函数:苹果吃完了
访问human的eat函数:<bound method Decorator.<locals>.eat of <__main__.Human object at 0x000001E40771B4C0>>

需要注意到:
  1.类的属性与实例的属性是互不相干的(创建实例后,Human.gender依然是男;通过human实例调用eat函数,human.eat()会报错,使用human.eat显示绑定装饰器方法)
      类可以直接调用属性,只有实例化后才能调用方法,然而上面这里实例化后是没有eat()属性的,所以human.eat()报错,但是这里实例化后实例是没有这个方法的(因为Human类中没有写)
  2.实例可以访问类的属性,同名的情况下优先访问实例的属性(human.age的结果是18)

  2.通过参数给类添加属性

类似于上面带参的函数装饰器,这里也只需要在类装饰器外面嵌套一层用于接收参数

def Decorator(**kwargs):
    def add(obj):
        "添加数据属性"
        # print('调用外部函数的**kwargs',kwargs)
        for key,val in kwargs.items():
            # 添加数据属性
            setattr(obj,key,val)
        return obj
    # print("外部传入的参数为:",kwargs)
    return add

#执行顺序:1.运行Decorator函数,先打印外部的传入的参数,返回add函数名;2.再执行School = add(School1)
@Decorator(addr = "浙江省杭州市",name ="浙江大学")          
class School1():
    def __init__(self,price):
        self.price =price

@Decorator(addr = "湖北省",price =12000)
class School2():
    pass

print(f"类School1的属性:\n{School1.__dict__}")
print(f"类School2的属性:\n{School2.__dict__}")
结果:
类School1的属性:
{'__module__': '__main__', '__init__': <function School1.__init__ at 0x000001EA4EC3BE50>, 
'__dict__': <attribute '__dict__' of 'School1' objects>, '__weakref__': <attribute '__weakref__' of 'School1' objects>, 
'__doc__': None, 'addr': '浙江省杭州市', 'name': '浙江大学'}
类School2的属性:
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'School2' objects>,
 '__weakref__': <attribute '__weakref__' of 'School2' objects>, '__doc__': None, 'addr': '湖北省', 'price': 12000}

可以看到属性已经添加成功
我们常见的内置装饰器有:@property、@staticmethod、@classmethod可以自行补充相关知识
参考:
https://www.zhihu.com/question/26930016
https://blog.51cto.com/10836356/2112490

posted @ 2020-10-29 22:00  luckytian  阅读(98)  评论(0)    收藏  举报