Python装饰器详解(附代码演示)

这段时间在练习使用python的django框架做个项目,里面经常出现装饰器,刚好之前装饰器的概念又模糊了,在这里写个笔记加固一下印象

什么是装饰器

简单来说装饰器就是用来修改(或者说丰富)函数的函数或者类,是的,装饰器可以是函数也可以是类。python中装饰器用于实现面向切面编程(AOP),以简化,规范代码,类似于Java中的注解(但又有不同)。

python中装饰器的实现得益于一切皆对象的理念,不同于Java,C++,在python中函数也可以像普通变量一样被赋值,当作参数传递。

装饰器的实现可以简单理解为多层函数的嵌套,最内层函数为原本函数,然后依次在外层函数中增加别的功能。以如下代码为例:


def use_logging(func):

    def wrapper():
        logging.warn("%s is running" % func.__name__)
        return func()   # 把 foo 当做参数传递进来时,执行func()就相当于执行foo()
    return wrapper

def foo():
    print('i am foo')

foo = use_logging(foo)  # 因为装饰器 use_logging(foo) 返回的时函数对象 wrapper,这条语句相当于  foo = wrapper
foo()                   # 执行foo()就相当于执行 wrapper()

以上代码用装饰器的语法糖@来实现就是:


def use_logging(func):

    def wrapper():
        logging.warn("%s is running" % func.__name__)
        return func()
    return wrapper

@use_logging
def foo():
    print("i am foo")

foo()

使用装饰器可以在不修改原来程序的逻辑,不对原程序做修改的情况下变更原程序的功能,提高了程序的利用率和可读性。

进阶用法

上面提到的装饰器用法只是最简单的,下面将介绍一些更贴近生产环境的用法

给业务逻辑函数传递参数

当业务逻辑函数需要传递参数时,上面实现的简单装饰器就无法满足需求了,这时可以用*args或者**kwargs

这里简单介绍下*args或者**kwargs的用法;*args用于指代不确定个数的参数,**kwargs为指定关键字的不确定个数参数。*args为列表(list),**kwargs为字典(dict)

示例代码如下:


def foo(name, age=None, height=None):
    print("I am %s, age %s, height %s" % (name, age, height))

给wrapper函数指定参数


def use_logging(func):

    def wrapper(*args, **kwargs):
        # args是一个列表,kwargs一个字典
        logging.warn("%s is running" % func.__name__)
        return func(*args, **kwargs)
    return wrapper

@use_logging
def foo():
    print("i am foo")

foo()

带参数的装饰器

装饰器也可以带参数,上面的装饰器解决了给业务逻辑函数传递参数的问题,带参数的装饰器则进一步丰富了装饰器的功能,比如logging装饰器,可以指定日志记录的等级,代码示例如下:

def use_logging(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == "warn":
                logging.warn("%s is running" % func.__name__)
            elif level == "info":
                logging.info("%s is running" % func.__name__)
            return func(*args)
        return wrapper

    return decorator

@use_logging(level="warn")
def foo(name='foo'):
    print("i am %s" % name)

foo()

带参数的装饰器其实时对原装饰器增加了一层函数封装,并返回一个装饰器(装饰器的本质也是函数或类)

类装饰器

前面也提到,装饰器可以是函数也可以是类,类装饰器具有灵活度大,高内聚,封装性等优点。使用类装饰器主要依靠类的__call__方法,当使用@形式将装饰器附加导函数上时,就会调用此方法

扫盲时刻:如果一个类Test实现了__call__方法,则该类的实例对象test(以test为例)将成为一个可调用对象。可调用对象指的是可以使用运算符()的对象,允许一个类的实例像函数一样被调用。实质上说,这意味着 t()t.__call__() 是相同的。同时 __call__ 参数可变。这意味着你可以定义 __call__ 为其他你想要的函数,无论有多少个参数。


# -*- coding: utf-8 -*-
class Entity:
    """调用实体来改变实体的位置。"""

    def __init__(self, size, x, y):
        self.x, self.y = x, y
        self.size = size

    def __call__(self, x, y):
        """改变实体的位置"""
        self.x, self.y = x, y
        print('call:', 'x=', self.x, 'y=', self.y)


class Temp:
    def __init__(self, x, y):
        self.x = x
        self.y = y


e = Entity(1, 2, 3)  # 创建实例
print(e.x, e.y)  # 输出为 2 3
e(4, 5)  # 实例可以象函数那样执行,并传入x y值,修改对象的x y;输出 call: x= 4 y= 5
print(callable(e))  # 输出 True
print('***********************')
t = Temp(1, 2)
print(callable(t))  # 输出 False

以上demo演示了__call__的作用,接下来看看类装饰器的示例(以计数为例)


# -*- coding: utf-8 -*-
class Counter:
    def __init__(self, func):
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        # 下面的return很重要,因为原函数func有返回值
        # 如果去掉return直接执行self.func(*args, **kwargs)
        # 会使得原函数丢失返回值
        return self.func(*args, **kwargs)  # 


@Counter
def foo():
    return 'hello'


for i in range(10):
    foo()

print(foo.count)  # 10

首先这里的 @Counter是装饰器,执行起来顺序是 foo = Counter(foo), 实例化,把foo函数传到类Counter里面,并存到对象属性里面,然后返回foo = Counter实例。 这时foo已经是Counter实例,而不是本身foo函数。
当执行foo()的时候,其实已经变成了,执行__call__函数,在__call__中会执行self.func() 即foo的实际逻辑,而且加上了计算调用次数,这样就记录了函数的状态

使用装饰器可以极大的提高代码的复用率,但是也存在缺点,就是会丢失原函数的元信息,比如__name__docstring,参数列表等,使用@wraps装饰器可以解决这个问题,用法如下:


from functools import wraps
def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print func.__name__      # 输出 'f'
        print func.__doc__       # 输出 'does some math'
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
   """does some math"""
   return x + x * x

装饰器顺序

一个函数可以同时使用多个装饰器,如下:


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

它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于f = a(b(c(f)))

demo示例如下:


def makeBold(fun):
    print('----a----')

    def inner():
        print('----1----')
        return '' + fun() + ''
    return inner
def makeItalic(fun):
    print('----b----')

    def inner():
        print('----2----')
        return '' + fun() + ''
    return inner
@makeBold
@makeItalic
def test():
    print('----c----')
    print('----3----')
    return 'hello python decorator'
ret = test()
print(ret)

输出结果为:


----b----
----a----
----1----
----2----
----c----
----3----
hello python decorator

@classmethod装饰器

使用@classmethod装饰器的函数不需要实例化,不需要self参数,但第一个参数需要是表示自身类的cls参数,可以来调用类的属性,类的方法,实例化对象等



class A(object):

    # 属性默认为类属性(可以给直接被类本身调用)
    num = "类属性"

    # 实例化方法(必须实例化类之后才能被调用)
    def func1(self): # self : 表示实例化类后的地址id
        print("func1")
        print(self)

    # 类方法(不需要实例化类就可以被类本身调用)
    @classmethod
    def func2(cls):  # cls : 表示没用被实例化的类本身
        print("func2")
        print(cls)
        print(cls.num)
        cls().func1()

    # 不传递传递默认self参数的方法(该方法也是可以直接被类调用的,但是这样做不标准)
    def func3():
        print("func3")
        print(A.num) # 属性是可以直接用类本身调用的
    
# A.func1() 这样调用是会报错:因为func1()调用时需要默认传递实例化类后的地址id参数,如果不实例化类是无法调用的
A.func2()
A.func3()

@staticmethod装饰器

@staticmethod装饰器修饰的函数常被定义在类中,被修饰后,不强制要求传递参数(用人能听得懂的说法是该函数不被实例化可以被调用)


#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
class C(object):
    @staticmethod
    def f():
        print('runoob');
 
C.f();          # 静态方法无需实例化,输出runoob
cobj = C()
cobj.f()        # 也可以实例化后调用,输出runoob

@proterty装饰器

@property装饰器会将方法转换为相同名称的只读属性(修饰方法,使方法可以像属性一样被访问);还可以与所定义的属性配合使用,可以防止属性被修改

示例1:


# -*- coding: utf-8 -*-
class DataSet(object):

    @property
    def method_with_property(self):  # 含有@property
        return 15

    def method_without_property(self):  # 不含@property
        return 15


demo = DataSet()
print(demo.method_with_property)  # 输出15,@property后,可以用调用属性的形式来调用方法,后面不需要加`()`。
print(demo.method_without_property())  # 没有加@property , 必须使用正常的调用方法的形式,即在后面加`()`
print(demo.method_without_property)  # 没有加@property,直接调用会输出该方法存放的地址
print(demo.method_with_property())  
# 如果使用property进行修饰后,又在调用的时候,方法后面添加了`()`
# 那么就会显示错误信息:TypeError: 'int' object is not callable,
# 也就是说添加@property 后,这个方法就变成了一个属性,如果后面加入了()
# 那么就是当作函数来调用,而它却不是callable(可调用)的。

由于python进行属性的定义时,没办法设置私有属性,因此要通过@property的方法来进行设置。这样可以隐藏属性名,让用户进行使用的时候无法随意修改。

示例2:


# -*- coding: utf-8 -*-
class DataSet(object):
    def __init__(self):
        self._images = 1
        self._labels = 2  # 定义属性的名称

    @property  # 使得images可读
    def images(self):  # 方法加入@property后,这个方法相当于一个属性,这个属性可以让用户进行使用,而且用户有没办法随意修改。
        return self._images

    @property  # 使得images可写
    def labels(self):
        return self._labels

    @labels.setter  # 使得images可删除
    def labels(self, value):
        self._labels = value

    @labels.deleter
    def labels(self):
        del self._labels


demo = DataSet()
# 用户进行属性调用的时候,直接调用images即可,而不用知道属性名_images,因此用户无法更改属性,从而保护了类的属性。
print(demo.images)  # 输出1,加了@property后,可以用调用属性的形式来调用方法,后面不需要加()。
print(demo.labels)  # 输出2
demo.labels = 3
print(demo.labels)  # 输出3
del demo.labels  # 删除属性_labels
print(demo.labels)  # 报错:AttributeError: 'DataSet' object has no attribute '_labels'
del demo.images  # 因为上一句报错,这句不会执行到,如果执行到的话,会报错:AttributeError: can't delete attribute

参考:https://www.runoob.com/w3cnote/python-func-decorators.html

参考:https://www.cnblogs.com/slysky/p/9777424.html

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值