Python 学习 ---> 装饰器 ( 例如: 超时、重试 )

​Python装饰器学习(九步入门):http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html
入浅出 Python 装饰器:16 步轻松搞定 Python 装饰器:https://blog.csdn.net/zhangliu463884153/article/details/80029101
Python装饰器高级版—Python类内定义装饰器并传递self参数:https://blog.51cto.com/yishi/2354752
入门Python装饰器:https://sikasjc.github.io/2018/09/17/pythondecorator/
深入Python装饰器:https://sikasjc.github.io/2018/09/25/deepindecorator/
一些更加实用的Python装饰器示例:https://sikasjc.github.io/2018/09/29/somedecorator/

Python 装饰器的八种写法:https://zhuanlan.zhihu.com/p/269012332

Python中一切皆对象。

要理解 python 的装饰器,我们首先必须明白 "在Python中,函数也是对象"

函数既然作为一个对象,因此:

  • 1. 函数可以被赋给其他变量
  • 2. 可以在函数中定义函数。就是 "函数嵌套"

这也就是说,函数可以返回一个函数。

既然可以返回一个函数,也可以把函数作为参数传递给函数

在面向对象(OOP)的设计模式中,decorator 被称为装饰模式。OOP 的装饰模式需要通过继承和组合来实现,而 Python 除了能支持 OOP 的 decorator 外,直接从语法层次支持 decorator。Python 的 decorator 可以用函数实现,也可以用类实现

decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。

1、Python 装饰器学习(九步入门)

第一步:最简单的函数,准备附加额外功能

示例代码:

"""
示例1: 最简单的函数,表示调用了两次
"""


def deco(func):
    print("before myfunc() called.")
    func()
    print("after myfunc() called.")
    return func


def myfunc():
    print("     myfunc() called.")


print('*****************************')
dec_func = deco(myfunc)

print('*****************************')
dec_func()

"""
*****************************
before myfunc() called.
     myfunc() called.
after myfunc() called.
*****************************
     myfunc() called.
"""

第二步:使用装饰函数在函数执行前和执行后分别附加额外功能

示例代码:

"""
示例2: 替换函数(装饰)
装饰函数的参数是被装饰的函数对象,返回原函数对象
装饰的实质语句: myfunc = deco(myfunc)
"""


def deco(func):
    print("before myfunc() called.")
    func()
    print("after myfunc() called.")
    return func


def myfunc():
    print("    myfunc() called.")


dec_func = deco(myfunc)
print('****************************')
dec_func()
dec_func()

"""
结果:
before myfunc() called.
    myfunc() called.
after myfunc() called.
****************************
    myfunc() called.
    myfunc() called.
"""

第三步:使用语法糖@来装饰函数

注意:时刻牢记 @deco 只是一个语法糖,其本质是 func = deco(func)

本例中 deco 没有使用内嵌函数,可以看到第一次执行可以进入到装饰函数,但是第二次不会进入装饰函数

"""
示例3: 使用语法糖@来装饰函数,相当于“myfunc = deco(myfunc)”
但发现新函数只在第一次被调用,且原函数多调用了一次
"""


def deco(func):
    print("before myfunc() called.")
    func()
    print("after myfunc() called.")
    return func


@deco
def myfunc():
    print("    myfunc() called.")


# 第一次调用后,返回的是 deco 里面的 func 对象,
# 所以第二次调用myfunc() 只输出 myfunc() called.
myfunc()  # 第一次调用
print('************************')
myfunc()  # 第二次调用

"""
结果:
before myfunc() called.
    myfunc() called.
after myfunc() called.
    myfunc() called.
************************
    myfunc() called.
"""

第四步:使用 嵌套函数 来确保 每次 新函数 都被调用

装饰函数 deco 返回内嵌包装函数对象 _deco。使用内嵌包装函数来确保每次新函数都被调用

"""
示例4: 使用内嵌包装函数来确保每次新函数都被调用,
内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象
"""


def deco(func):
    def _deco():
        print("before myfunc() called.")
        func()
        print("after myfunc() called.")
        # 不需要返回func,实际上应返回原函数的返回值
    return _deco


@deco
def myfunc():
    print("    myfunc() called.")
    return 'ok'


myfunc()
print('*************************')
myfunc()

"""
执行结果:
before myfunc() called.
    myfunc() called.
after myfunc() called.
*************************
before myfunc() called.
    myfunc() called.
after myfunc() called.
"""

示例代码:无参数的函数( 被装饰的函数没有参数

# decorator.py

from time import ctime, sleep


def time_fun(func):
    def wrapped_func():
        print(f"{func.__name__} called at {ctime()}")
        return func()
    return wrapped_func


@time_fun
def foo():
    pass


foo()
sleep(2)
foo()

第五步:对 带参数的函数 进行装饰( 被装饰的函数带参数 

示例代码:

"""
示例5: 对带参数的函数进行装饰,
内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象
"""


def deco(func):
    def _deco(a, b):
        print("before myfunc() called.")
        ret = func(a, b)
        print("after myfunc() called. result: %s" % ret)
        return ret

    return _deco


@deco
def myfunc(a, b):
    print("    myfunc(%s,%s) called." % (a, b))
    return a + b


myfunc(1, 2)
print('*************************')
myfunc(3, 4)

"""
执行结果:
before myfunc() called.
    myfunc(1,2) called.
after myfunc() called. result: 3
*************************
before myfunc() called.
    myfunc(3,4) called.
after myfunc() called. result: 7
"""

示例代码:被装饰的函数带参数 

# decorator2.py

from time import ctime, sleep


def time_fun(func):
    def wrapped_func(a, b):
        print(f"{func.__name__} called at {ctime()}")
        print(a, b)
        return func(a, b)

    return wrapped_func


@time_fun
def foo(a, b):
    print(a + b)


foo(3, 5)
sleep(2)
foo(2, 4)

第六步:对 参数数量不确定 的函数进行装饰

示例代码:

"""
示例 6: 对参数数量不确定的函数进行装饰,
参数用(*args, **kwargs),自动适应变参和命名参数
"""


def deco(func):
    def _deco(*args, **kwargs):
        print("before %s called." % func.__name__)
        ret = func(*args, **kwargs)
        print("after %s called. result: %s" % (func.__name__, ret))
        return ret

    return _deco


@deco
def myfunc_1(a, b):
    print("    myfunc_1(%s,%s) called." % (a, b))
    return a + b


@deco
def myfunc_2(a, b, c):
    print("    myfunc_2(%s,%s,%s) called." % (a, b, c))
    return a + b + c


print('*' * 30)
myfunc_1(1, 2)
print('*' * 30)
myfunc_1(3, 4)
print('*' * 30)
myfunc_2(1, 2, 3)
print('*' * 30)
myfunc_2(3, 4, 5)

"""
执行结果:
******************************
before myfunc_1 called.
    myfunc_1(1,2) called.
after myfunc_1 called. result: 3
******************************
before myfunc_1 called.
    myfunc_1(3,4) called.
after myfunc_1 called. result: 7
******************************
before myfunc_2 called.
    myfunc_2(1,2,3) called.
after myfunc_2 called. result: 6
******************************
before myfunc_2 called.
    myfunc_2(3,4,5) called.
after myfunc_2 called. result: 12
"""

第七步:让 装饰器 带 参数

示例代码:

"""
示例 7: 在示例4的基础上,让装饰器带参数,
和上一示例相比在外层多了一层包装。
装饰函数名实际上应更有意义些
"""


def deco(arg):
    def _deco(func):
        def __deco():
            print("before %s called [%s]." % (func.__name__, arg))
            func()
            print("after %s called [%s]." % (func.__name__, arg))

        return __deco

    return _deco


@deco("mymodule")
def myfunc():
    print("    myfunc_1() called.")


@deco("module2")
def myfunc2():
    print("    myfunc_2() called.")


print('************************************')
myfunc()
print('************************************')
myfunc2()

"""
执行结果:
************************************
before myfunc called [mymodule].
    myfunc_1() called.
after myfunc called [mymodule].
************************************
before myfunc2 called [module2].
    myfunc_2() called.
after myfunc2 called [module2].
"""

装饰器带参数,在原有装饰器的基础上,设置外部变量

from time import ctime, sleep


def time_fun_arg(pre="hello"):
    def time_fun(func):
        def wrapped_func():
            print("%s called at %s %s"%(func.__name__, ctime(), pre))
            return func()
        return wrapped_func
    return time_fun


@time_fun_arg("12345")
def foo():
    pass


@time_fun_arg("abcde")
def too():
    pass


foo()
sleep(2)
foo()


too()
sleep(2)
too()

第八步:让 装饰器 带 类 参数

示例代码:

"""
示例8: 装饰器带类参数
"""


class Locker:
    def __init__(self):
        print("locker.__init__() should be not called.")

    @staticmethod
    def acquire():
        print("locker.acquire() called.(这是静态方法)")

    @staticmethod
    def release():
        print("locker.release() called.(不需要对象实例)")


def deco(cls):
    '''cls 必须实现acquire和release静态方法'''

    def _deco(func):
        def __deco():
            print("before %s called [%s]." % (func.__name__, cls))
            cls.acquire()
            try:
                return func()
            finally:
                cls.release()

        return __deco

    return _deco


@deco(Locker)
def myfunc():
    print("    myfunc() called.")


print('*********************************************')
myfunc()
print('*********************************************')
myfunc()

"""
执行结果:
*********************************************
before myfunc called [<class '__main__.Locker'>].
locker.acquire() called.(这是静态方法)
    myfunc() called.
locker.release() called.(不需要对象实例)
*********************************************
before myfunc called [<class '__main__.Locker'>].
locker.acquire() called.(这是静态方法)
    myfunc() called.
locker.release() called.(不需要对象实例)
"""

装饰器 和 闭包 混用:

# coding=utf-8
from time import time


def logged(when):
    def log(f, *args, **kargs):
        print("fun:%s args:%r kargs:%r" % (f, args, kargs))
        # %r字符串的同时,显示原有对象类型

    def pre_logged(f):
        def wrapper(*args, **kargs):
            log(f, *args, **kargs)
            return f(*args, **kargs)
        return wrapper

    def post_logged(f):
        def wrapper(*args, **kargs):
            now = time()
            try:
                return f(*args, **kargs)
            finally:
                log(f, *args, **kargs)
                print("time delta: %s" % (time() - now))
        return wrapper

    try:
        return {"pre": pre_logged, "post": post_logged}[when]
    except BaseException as e:
        print('must be "pre" or "post"')
        raise e


@logged("post")
def fun(name):
    print("Hello, ", name)


fun("world!")

第九步:装饰器带类参数,并分拆公共类到其他py文件中,

同时 演示了对一个函数应用多个装饰器

mylocker.py

class MyLocker:
    def __init__(self):
        print("mylocker.__init__() called.")

    @staticmethod
    def acquire():
        print("mylocker.acquire() called.")

    @staticmethod
    def unlock():
        print("mylocker.unlock() called.")


class LockerEx(MyLocker):
    @staticmethod
    def acquire():
        print("lockerex.acquire() called.")

    @staticmethod
    def unlock():
        print("lockerex.unlock() called.")


def lock_helper(cls):
    """cls 必须实现acquire和release静态方法"""

    def _deco(func):
        def __deco(*args, **kwargs):
            print("before %s called." % func.__name__)
            cls.acquire()
            try:
                return func(*args, **kwargs)
            finally:
                cls.unlock()

        return __deco

    return _deco

测试代码:

"""
示例 9: 装饰器带类参数,并分拆公共类到其他py文件中
同时演示了对一个函数应用多个装饰器
"""


class Example:
    @lock_helper(MyLocker)
    def func_1(self):
        print("\nfunc_1() called.")

    @lock_helper(MyLocker)
    @lock_helper(LockerEx)
    def func_2(self, a, b):
        print("\nfunc_2() called.")
        return a + b


if __name__ == "__main__":
    a = Example()
    a.func_1()
    print(a.func_1())
    print(a.func_2(1, 2))
    print(a.func_2(3, 4))


"""
执行结果:
before func_1 called.
mylocker.acquire() called.
    func_1() called.
mylocker.unlock() called.
before func_1 called.
mylocker.acquire() called.
    func_1() called.
mylocker.unlock() called.
None
before __deco called.
mylocker.acquire() called.
before func_2 called.
lockerex.acquire() called.
    func_2() called.
lockerex.unlock() called.
mylocker.unlock() called.
3
before __deco called.
mylocker.acquire() called.
before func_2 called.
lockerex.acquire() called.
    func_2() called.
lockerex.unlock() called.
mylocker.unlock() called.
7
"""

2、Python 装饰器的 4 种类型

  • 函数  装饰  函数
  • 函数  装饰 
  •   装饰  函数
  •   装饰 

@wraps(func) 要使用这个必须导入functools,这个作用是消除(被装饰后的函数名等属性的改变)副作用

参考:https://blog.csdn.net/freeking101/article/details/109662542

from functools import wraps


def decorate(src_func):
    @wraps(src_func)
    def wrapper():
        print("hello")
        src_func()
        print("world")

    return wrapper


@decorate
def func_test():
    print("2019-12-31")


print(func_test.__name__, func_test.__doc__)

# 加上 @wraps(src_func)   输出为 func_test None
# 不加  输出为 wrapper None

1. 函数 装饰 函数

def wrapFun(func):
    def inner(a, b):
        print('function name:', func.__name__)
        r = func(a, b)
        return r
    return inner

@wrapFun
def myadd(a, b):
    return a + b

print(myadd(2, 3))

装饰不带参数的函数

# -*- coding: utf-8 -*-

def clothes(func):
    def wear():
        print('Buy clothes!{}'.format(func.__name__))
        return func()
    return wear


@clothes
def body():
    print('The body feels cold!')

body()
    
#备注:@是语法糖
# 不用语法糖的情况下,使用下面语句也能实现装饰作用:把body再加工,再传给body
# body = clothes(body)

示例 :

def deco(func):
    def _deco(*args, **kwargs):
        print('call deco')
        func(*args, **kwargs)
    return _deco


@deco
def test():
    print('call test')

# 等同于
# def test():
#     print('call test')
# test = deco(func)

装饰带一个参数的函数

# -*- coding: utf-8 -*-

def clothes(func):
    def wear(anything):  # 实际是定义一个anything参数,对应body函数参数
        print('Buy clothes!{}'.format(func.__name__))
        return func(anything)  # 执行func函数,并传入调用传入的anything参数
        # wear = func(anything)    # 在这一步,实际上可以看出来,为啥wear必须带参数,因为它就是func(anything)

    return wear
    # 所以clothes的结果是
    # clothes = wear = func(anything)
    # 不用语法糖的情况下就是
    # body = clothes(body)('hands')
    # 进一步证明:print(body.__name__)  显示的是wear函数


@clothes
def body(part):
    print('The body feels could!{}'.format(part))


body('hands')

装饰带不定长参数的函数

通常装饰器不只装饰一个函数,每个函数参数的个数也不相同,这个时候使用不定长参数*args,**kwargs

def clothes(func):
    def wear(*args, **kwargs):
        print('Buy clothes!{}'.format(func.__name__))
        return func(*args, **kwargs)

    return wear


@clothes
def body(part):
    print('The body feels could!{}'.format(part))


@clothes
def head(head_wear, num=2):
    print('The head need buy {} {}!'.format(num, head_wear))


body('hands')
head('headdress')

装饰器带参数

# 把装饰器再包装,实现了seasons传递装饰器参数。

def seasons(season_type):
    def clothes(func):
        def wear(*args, **kwargs):
            if season_type == 1:
                s = 'spring'
            elif season_type == 2:
                s = 'summer'
            elif season_type == 3:
                s = 'autumn'
            elif season_type == 4:
                s = 'winter'
            else:
                print('The args is error!')
                return func(*args, **kwargs)
            print('The season is {}!{}'.format(s, func.__name__))
            return func(*args, **kwargs)

        return wear

    return clothes


@seasons(2)
def children():
    print('i am children')

示例:

def deco(*args, **kwargs):
    def _deco(func):
        print(args, kwargs)
        def __deco(*args, **kwargs):
            print('call deco')
            func(*args, **kwargs)
        return __deco
    return _deco


@deco('hello', x='ni hao')
def test():
    print('call test')


# 等同于
# def test():
#     print('call test')
# test = deco('hello', x='ni hao')(test)

2. 函数 装饰 类

简单示例:


def bar(self=None, func_args=None):
    print('bar')
    print(self)
    print(func_args)


def inject(cls):
    cls.bar = bar
    return cls


@inject
class Foo(object):
    pass


foo = Foo()
foo.bar(111)

"""
bar
<__main__.Foo object at 0x0000022D83DA7D60>
111
"""

上述代码的 inject 装饰器为类动态的添加一个 bar 方法,因为类在调用非静态方法的时候会传进一个self 指针,因此 bar 的第一个参数我们简单的忽略即可。运行结果:bar

示例:

def deco(*args, **kwargs):
    def _deco(cls):
        cls.x = 12
        return cls
    return _deco

@deco('hello')
class A(object):
    pass

>>> A.x
12

# 等同于
# class A(object):
#     pass
# A = deco('hello')(A)

示例:

def wrapper_class(cls):
    def inner(a):
        print('class name:', cls.__name__)
        return cls(a)

    return inner


@wrapper_class
class Foo(object):
    def __init__(self, a):
        self.a = a

    def fun(self):
        print('self.a =', self.a)


m = Foo('Are you OK!')
m.fun()

"""
class name: Foo
self.a = Are you OK!
"""

示例:

创建单例(Singletons)

单例是一个只有一个实例的类。Python中有几种常用的单例,包括NoneTrueFalse。事实上,None是一个单例,允许你使用is关键字比较None

示例:下面的 @singleton 装饰器将类的第一个实例存储为属性,从而将类转换为单例对象。稍后创建实例的尝试只是返回存储的实例:

import functools

def singleton(cls):
    """Make a class a Singleton class (only one instance)"""
    @functools.wraps(cls)
    def wrapper_singleton(*args, **kwargs):
        if not wrapper_singleton.instance:
            wrapper_singleton.instance = cls(*args, **kwargs)
        return wrapper_singleton.instance
    wrapper_singleton.instance = None
    return wrapper_singleton

@singleton
class TheOne:
    pass

如上所示,这个类装饰器遵循与我们的函数装饰器相同的模板。唯一的区别是,我们使用cls作为参数名来表示它是一个类装饰器,而不是func

运行效果:

>>> first_one = TheOne()
>>> another_one = TheOne()

>>> id(first_one)
140094218762280

>>> id(another_one)
140094218762280

>>> first_one is another_one
True

很明显,first_one确实与another_one完全相同。

在Python中,单例类的使用并不像在其他语言中那样频繁。单例的效果通常在模块中作为全局变量更好地实现。

3.  装饰 函数方法

装饰 函数

定义一个类装饰器,装饰函数,默认调用 __call__ 方法

class Decorator(object):
    def __init__(self, func):  # 传送的是test方法
        self.func = func

    def __call__(self, *args, **kwargs):  # 接受任意参数
        print('函数调用CALL')
        return self.func(*args, **kwargs)  # 适应test的任意参数        


@Decorator  # 如果带参数,init中的func是此参数。
def test(hh):
    print('this is the test of the function !', hh)


test('hh')

示例:

class ShowFunName(object):
    def __init__(self, func):
        self._func = func

    def __call__(self, a):
        print('function name:', self._func.__name__)
        return self._func(a)


@ShowFunName
def bar(a):
    return a

print(bar('Are you OK'))

无参数

class Deco(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print('call Deco')
        self.func(*args, **kwargs)


@Deco
def test():
    print('call test')


# 等同于
test = Deco(test)

有参数

class Deco(object):
    def __init__(self, *args, **kwargs):
        print(args, kwargs)

    def __call__(self, func):
        def _deco(*args, **kwargs):
            print('call Deco')
            func(*args, **kwargs)

        return _deco


@Deco('hello')
def test():
    print('call test')

# 等同于
# test = Deco('hello')(func)

示例:

类装饰器 相比 函数装饰器,具有灵活度大,高内聚、封装性等优点。其实现起来主要是靠类内部的 __call__ 方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法,下面时一个实例:

class Foo(object):
    def __init__(self, func):
        super(Foo, self).__init__()
        self._func = func

    def __call__(self):
        print('class decorator')
        self._func()


@Foo
def bar():
    print('bar')


bar()

运行结果如下:

class decorator
bar

装饰 方法

    定义一个类装饰器,装饰类中的函数,默认调用__get__方法

    实际上把类方法变成属性了,还记得类属性装饰器吧,@property

    下面自已做一个property

class Decorator(object):
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):
        """
        instance:代表实例,sum中的self
        owner:代表类本身,Test类
        """
        print('调用的是get函数')
        return self.func(instance)  # instance就是Test类的self


class Test(object):
    def __init__(self):
        self.result = 0

    @Decorator
    def sum(self):
        print('There is the Func in the Class !')


t = Test()
print(t.sum)  # 众所周知,属性是不加括号的,sum真的变成了属性

示例:

做一个求和属性sum,统计所有输入的数字的和

class Decorator(object):
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):
        print('调用的是get函数')
        return self.func(instance)


class Test(object):
    def __init__(self, *args, **kwargs):
        self.value_list = []
        if args:
            for i in args:
                if str(i).isdigit():
                    self.value_list.append(i)
        if kwargs:
            for v in kwargs.values():
                if str(v).isdigit():
                    self.value_list.append(v)

    @Decorator
    def sum(self):
        result = 0
        print(self.value_list)
        for i in self.value_list:
            result += i

        return result


t = Test(1, 2, 3, 4, 5, 6, 7, 8, i=9, ss=10, strings='lll')

print(t.sum)

无参数

class Deco(object):
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):
        def _deco(*args, **kwargs):
            print('call Deco')
            self.func(instance, *args, **kwargs)

        return _deco


class A(object):
    @Deco
    def test(self):
        print('call test')


# 等同于
# class A(object):
#     def test(self):
#         print('call test')
#     test = Deco(test)

有参数

class Deco(object):
    def __init__(self, *args, **kwargs):
        print(args, kwargs)

    def __get__(self, instance, owner):
        def _deco(*args, **kwargs):
            print('call Deco')
            self.func(instance, *args, **kwargs)

        return _deco

    def __call__(self, func):
        self.func = func
        return self


class A(object):

    @Deco('hello')
    def test(self):
        print('call test')


# 等同于
# class A(object):
#     def test(self):
#         print('call test')
#     test = Deco('hello')(test)

内置装饰器 ( @staticmethod、@classmethod、@property )

Python 中内置的装饰器有三个: staticmethodclassmethod 和 property

staticmethod 是类静态方法,其跟成员方法的区别是没有 self 指针,并且可以在类不进行实例化的情况下调用,

下面是一个实例,对比静态方法 和 成员方法:

class Foo(object):

    def __init__(self):
        super(Foo, self).__init__()
        self.msg = 'hello word'

    @staticmethod
    def static_method(msg):
        print(msg)

    def member_method(self, msg=None):
        msg = msg if msg else self.msg
        print(msg)


foo = Foo()
foo.member_method('some msg')
foo.static_method('some msg')
Foo.static_method('some msg')

classmethod 与成员方法的区别在于所接收的第一个参数不是 self 类实例的指针,而是当前类的具体类型,

下面是一个实例:

class Foo(object):
    @classmethod
    def class_method(cls):
        print(repr(cls))

    def member_method(self):
        print(repr(self))


foo = Foo()
foo.class_method()
foo.member_method()

运行结果如下:

<class '__main__.Foo'>
<__main__.Foo object at 0x000002895A412508>

property 是属性的意思,即可以通过通过类实例直接访问的信息,下面是具体的例子:

class Foo(object):
    def __init__(self, var):
        super(Foo, self).__init__()
        self._var = var

    @property
    def var(self):
        return self._var

    @var.setter
    def var(self, var):
        self._var = var


foo = Foo('var 1')
print(foo.var)
foo.var = 'var 2'
print(foo.var)

注意: 如果将上面的 @var.setter 装饰器所装饰的成员函数去掉,则 Foo.var 属性为只读属性,使用 foo.var = 'var 2' 进行赋值时会抛出异常,其运行结果如下:

var 1
var 2

4. 类 装饰 类

class ShowClassName(object):
    def __init__(self, cls):
        self._cls = cls

    def __call__(self, a):
        print('class name:', self._cls.__name__)
        return self._cls(a)


@ShowClassName
class Foobar(object):
    def __init__(self, a):
        self.value = a

    def fun(self):
        print(self.value)


a = Foobar('Are you OK')
a.fun()

5. 函数 装饰 类中的方法

示例代码 1:

From:https://www.cnblogs.com/xieqiankun/p/python_decorate_method.html

import time
import datetime
import requests


def cost_time(file_name, f_or_m=1):
    def _deco(origin_func):
        if 'f' == f_or_m:
            def wrapper(*args, **kwargs):
                start_time = datetime.datetime.now()
                ret_val = origin_func(*args, **kwargs)
                end_time = datetime.datetime.now()
                ct = (end_time - start_time).seconds
                print(f'[{file_name}][{origin_func.__name__}] cost_time:{ct}')
                return ret_val
            return wrapper
        elif 'm' == f_or_m:
            def wrapper(self, *args, **kwargs):
                start_time = datetime.datetime.now()
                ret_val = origin_func(self, *args, **kwargs)
                end_time = datetime.datetime.now()
                ct = (end_time - start_time).seconds
                print(f'[{file_name}][{origin_func.__name__}] cost_time:{ct}')
                return ret_val
            return wrapper
    return _deco


class Test(object):
    def __init__(self):
        pass

    @cost_time(__file__, 'm')
    def test_1(self):
        time.sleep(2)
        pass

    @cost_time(__file__, 'm')
    def test_2(self):
        r = requests.get('http://www.baidu.com')
        if 200 == r.status_code:
            print(r.status_code)
        else:
            print(r.status_code)


if __name__ == '__main__':
    t = Test()
    t.test_1()
    t.test_1()
    t.test_2()
    pass

运行结果:

[test_1] cost_time:3
[test_1] cost_time:3
200
[test_2] cost_time:0

示例代码 2:

From:https://www.kingname.info/2017/04/17/decorate-for-method/

使用 Python 的装饰器装饰一个类的方法,同时在装饰器函数中调用类里面的其他方法。这里以捕获一个方法的异常为例来进行说明。

有一个类Test, 它的结构如下:

class Test(object):
    def __init__(self):
        pass

    def revive(self):
        print('revive from exception.')
        # do something to restore

    def read_value(self):
        print('here I will do something.')
        # do something.

在类中有一个方法read_value(),这个方法在多个地方被调用。由于某些原因,方法read_value有可能随机抛出Exception导致程序崩溃。所以需要对整个方法做try ... except处理。最丑陋的做法如下面的代码所示:

class Test(object):
    def __init__(self):
        pass

    def revive(self):
        print('revive from exception.')
        # do something to restore

    def read_value(self):
        try:
            print('here I will do something.')
            # do something.
        except Exception as e:
            print(f'exception {e} raised, parse exception.')
            # do other thing.
            self.revive()

这样写虽然可以解决问题,但是代码不Pythonic。

使用装饰器来解决这个问题,装饰器函数应该写在类里面还是类外面呢?答案是,写在类外面。那么既然写在类外面,如何调用这个类的其他方法呢?

首先写出一个最常见的处理异常的装饰器:

def catch_exception(origin_func):
    def wrapper(*args, **kwargs):
        try:
            u = origin_func(*args, **kwargs)
            return u
        except Exception:
            return 'an Exception raised.'
    return wrapper


class Test(object):
    def __init__(self):
        pass

    def revive(self):
        print('revive from exception.')
        # do something to restore

    @catch_exception
    def read_value(self):
        print('here I will do something.')
        # do something.

这种写法,确实可以捕获到 origin_func()的异常,但是如果在发生异常的时候,需要调用类里面的另一个方法来处理异常,这又应该怎么办?答案是给 wrapper 增加一个参数:self.

代码变为如下形式:

def catch_exception(origin_func):
    def wrapper(self, *args, **kwargs):
        try:
            u = origin_func(self, *args, **kwargs)
            return u
        except Exception:
            self.revive() #不用顾虑,直接调用原来的类的方法
            return 'an Exception raised.'
    return wrapper


class Test(object):
    def __init__(self):
        pass

    def revive(self):
        print('revive from exception.')
        # do something to restore

    @catch_exception
    def read_value(self):
        print('here I will do something.')
        # do something.

执行结果:

示例代码 3:

import functools


def auto_retry(src_func):

    @functools.wraps(src_func)
    def wrapper(*args, **kwargs):
        for i in range(3):
            try:
                return src_func(*args, **kwargs)
            except Exception as e:
                print(src_func.__name__, e)

    return wrapper


class TestClass(object):
    def __init__(self):
        pass

    def __del__(self):
        pass

    @auto_retry
    def crawl(self, url=None):
        raise Exception('crawl exception')
        pass

TestClass().crawl()

6. 类中的方法 装饰 方法函数

  • 方法:类中的成员函数叫做 方法
  • 函数:不在类中的函数,即普通函数叫做 函数

类中的方法 装饰 函数

在类里面定义个函数,用来装饰其它函数,严格意义上说不属于类装饰器。

class Buy(object):
    def __init__(self, func):
        self.func = func

    # 在类里定义一个函数
    def clothes(func):  # 这里不能用self,因为接收的是body函数
        # 其它都和普通的函数装饰器相同
        def ware(*args, **kwargs):
            print('This is a decorator!')
            return func(*args, **kwargs)

        return ware


@Buy.clothes
def body(hh):
    print('The body feels could!{}'.format(hh))


body('hh')

类中的方法 装饰 类中的方法

    背景:想要通过装饰器修改类里的self属性值。

class Buy(object):
    def __init__(self):
        self.reset = True  # 定义一个类属性,稍后在装饰器里更改
        self.func = True

        # 在类里定义一个装饰器

    def clothes(func):  # func接收body
        def ware(self, *args, **kwargs):  # self,接收body里的self,也就是类实例
            print('This is a decrator!')
            if self.reset == True:  # 判断类属性
                print('Reset is Ture, change Func..')
                self.func = False  # 修改类属性
            else:
                print('reset is False.')

            return func(self, *args, **kwargs)

        return ware

    @clothes
    def body(self):
        print('The body feels could!')


b = Buy()  # 实例化类
b.body()  # 运行body
print(b.func)  # 查看更改后的self.func值,是False,说明修改完成

3、装饰器 神库 decorator

安装:pip install decorator

有了它之后,会发现以后自己定义的装饰器,就再也不需要写嵌套的函数了

from decorator import decorator

@decorator
def deco(func, *args, **kw):
    print("Ready to run task")
    func(*args, **kw)
    print("Successful to run task")

@deco
def myfunc():
    print("Running the task")

myfunc()

deco 作为装饰函数,第一个参数是固定的,都是指被装饰函数,而后面的参数都固定使用 可变参数 *args 和 **kw 的写法,代码被装饰函数的原参数。这种写法,更加符合直觉,代码的逻辑也更容易理解。

带参数的装饰器

from decorator import decorator

@decorator
def warn_slow(func, timelimit=60, *args, **kw):
    t0 = time.time()
    result = func(*args, **kw)
    dt = time.time() - t0
    if dt > timelimit:
        logging.warn('%s took %d seconds', func.__name__, dt)
    else:
        logging.info('%s took %d seconds', func.__name__, dt)
    return result

@warn_slow(timelimit=600)  # warn if it takes more than 10 minutes
def run_calculation(tempdir, outdir):
    pass

可以看到

  • 装饰函数的第一个参数,还是被装饰器 func ,这个跟之前一样
  • 而第二个参数 timelimit 写成了位置参数的写法,并且有默认值
  • 再往后,就还是跟原来一样使用了可变参数的写法

只要你在装饰函数中第二个参数开始,使用了非可变参数的写法,这些参数就可以做为装饰器调用时的参数。

在自己写装饰器的时候,通常都会顺手加上一个叫 functools.wraps 的装饰器

def wrapper(func):
    def inner_function():
        pass
    return inner_function

@wrapper
def wrapped():
    pass

print(wrapped.__name__)
#inner_function

为什么会这样子?不是应该返回 func 吗?这也不难理解,因为上边执行func 和下边 decorator(func) 是等价的,所以上面 func.__name__ 是等价于下面decorator(func).__name__ 的,那当然名字是 inner_function

def wrapper(func):
    def inner_function():
        pass
    return inner_function

def wrapped():
    pass

print(wrapper(wrapped).__name__)
#inner_function

可以看到当一个函数被装饰器装饰过后,它的签名信息会发生变化(譬如上面看到的函数名)

解决方案就是使用我们前面所说的 functools .wraps 装饰器。它的作用就是将 被修饰的函数(wrapped) 的一些属性值赋值给 修饰器函数(wrapper) ,最终让属性的显示更符合我们的直觉。

from functools import wraps

def wrapper(func):
    @wraps(func)
    def inner_function():
        pass
    return inner_function

@wrapper
def wrapped():
    pass

print(wrapped.__name__)
# wrapped

那么使用了 decorator 之后,是否还会存在这种签名的问题呢?验证一下就知道啦

from decorator import decorator

@decorator
def deco(func, *args, **kw):
    print("Ready to run task")
    func(*args, **kw)
    print("Successful to run task")

@deco
def myfunc():
    print("Running the task")

print(myfunc.__name__)

输出的结果是 myfunc,说明 decorator 已经默认帮我们处理了一切可预见的问题。

4、前置知识 --- 闭 包

什么是闭包?

内部函数对外部函数作用域里变量的引用(非全局变量),则称内部函数为闭包。

javascript 闭包:https://www.runoob.com/js/js-function-closures.html

  • 闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰,即可以让函数内的局部变量常驻内存而不被销毁。直观的说:就是形成一个不销毁的栈环境在某些方面,你也许会把它当做一个类似于面向对象的技术:outer像是给inner服务的构造器,x像一个私有变量。闭包会捕获其环境。闭包closures)是可以保存在一个变量中或作为参数传递给其他函数的匿名函数。可以在一个地方创建闭包,然后在不同的上下文中执行闭包运算。不同于函数,闭包允许捕获被定义时所在作用域中的值。
  • 闭包在维基百科上的定义如下:在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法:认为闭包是由函数和与其相关的引用环境组合而成的实体。  

示例:

def outer(arg=2):
    x = 1

    def inner():
        nonlocal x, arg
        x += 1
        arg += 1
        print(f"{x}, {arg}")  # 1

    return inner


func = outer()
func()
func()
func()
func()

上面代码可以看出 x 和 arg 都是常驻内存,每次执行 func() 时都加 1

def outer():
    x = 1

    def inner():
        print(x)  # 1

    return inner


val = outer()
print(f"val ---> {val}")
print(f"val() ---> {val()}")
print(f"val.__closure__ ---> {val.__closure__}")

inner 作为一个函数被 outer 返回,保存在变量 foo 中,并且我们能够对它进行调用foo()。

上面代码都是在 python 的作用域规则下进行工作:

“x是函数outer里的一个局部变量。当函数inner在#1处打印x的时候,python解释器会在inner内部查找相应的变量,当然会找不到,所以接着会到封闭作用域里面查找,并且会找到匹配。

但是从变量的生存周期来看,该怎么理解呢?我们的变量x是函数outer的一个本地变量,这意味着只有当函数outer正在运行的时候才会存在。根据我们已知的python运行模式,我们没法在函数outer返回之后继续调用函数inner,在函数inner被调用的时候,变量x早已不复存在,可能会发生一个运行时错误。

万万没想到,返回的函数inner居然能够正常工作。Python支持一个叫做函数闭包的特性,用人话来讲就是,嵌套定义在非全局作用域里面的函数能够记住它在被定义的时候它所处的封闭命名空间。这能够通过查看函数的 __closure__ 属性得出结论,这个属性里面包含封闭作用域里面的值(只会包含被捕捉到的值,比如x,如果在outer里面还定义了其他的值,封闭作用域里面是不会有的)

记住,每次函数outer被调用的时候,函数inner都会被重新定义。现在变量x的值不会变化,所以每次返回的函数inner会是同样的逻辑,假如我们稍微改动一下呢?

def outer(x):
    def inner():
        print(x)  # 1
    return inner
print1 = outer(1)
print2 = outer(2)
print1()
1
print2()
2

从这个例子中你能够看到 "闭包 – 被函数记住的封闭作用域 – 能够被用来创建自定义的函数",本质上来说是一个硬编码的参数。事实上我们并不是传递参数1或者2给函数inner,我们实际上是创建了能够打印各种数字的各种自定义版本。

使用闭包的方式也有很多:你如果熟悉python内置排序方法的参数key,你说不定已经写过一个lambda方法在排序一个列表的列表的时候基于第二个元素而不是第一个。现在你说不定也可以写一个itemgetter方法,接收一个索引值来返回一个完美的函数,传递给排序函数的参数key。

在某些方面,你也许会把它当做一个类似于面向对象的技术:outer像是给inner服务的构造器,x像一个私有变量。下面给出一个使用闭包实现的 logger factory 的例子:


def logger_factory(prefix="", with_prefix=True):
    if with_prefix:
        def logger(msg):
            print(prefix + msg)
        return logger
    else:
        def logger(msg):
            print(msg)
        return logger


logger_with_prefix = logger_factory("Prefix: ")
logger_without_prefix = logger_factory(with_prefix=False)
logger_with_prefix("msg")
logger_without_prefix("msg")

运行结果:

Prefix: msg
msg

在上面这个闭包的例子中,prefix 变量时所谓的自由变量,其在 return logger 执行完毕后,便脱离了创建它的环境logger_factory,但因为其被logger_factory 中定义的 logger 函数所引用,其生命周期将至少和 logger 函数相同。这样,在 logger 中就可以引用到logger_factory 作用域内的变量 prefix

将 闭包 与 namespace 结合起来 示例:

var = "var in global"


def fun_outer():
    var = "var in fun_outer"
    unused_var = "this var is not used in fun_inner"
    print("fun_outer: " + var)
    print("fun_outer: " + str(locals()))    
    print("fun_outer: " + str(id(var)))

    def fun_inner():
        print("fun_inner: " + var)
        print("fun_inner: " + str(locals()))
        print("fun_inner: " + str(id(var)))
    return fun_inner


fun_outer()()

运行结果如下:

fun_outer: var in fun_outer
fun_outer: {'unused_var': 'this var is not used in fun_inner', 'var': 'var in fun_outer'}
fun_outer: 2543733314784
fun_inner: var in fun_outer
fun_inner: {'var': 'var in fun_outer'}
fun_inner: 2543733314784

在这个例子中,当 fun_outer 被定义时,其内部的定义的 fun_inner 函数对 print "fun_inner: " + var 中所引用的 var 变量进行搜索,发现第一个被搜索到的 var 定义在 fun_outer 的 local namespace 中,因此使用此定义,通过 print "fun_outer: " + str(id(var)) 和 print "fun_inner: " + str(id(var)),当var 超出 fun_outer 的作用域后,依然存活,而 fun_outer 中的unused_var 变量由于没有被 fun_inner 所引用,因此会被 GC


 

Python 闭包示例

示例 1:

def make_adder(arg_1=None):
    def adder(arg_2=None):
        return arg_1 + arg_2
    return adder


p = make_adder(23)
q = make_adder(44)

print(p(100))
print(q(100))


# 运行结果:
# 123
# 144

分析:

我们发现 make_adder 是一个函数,包括一个参数 arg_1 ,比较特殊的地方是这个函数里面又定义了一个新函数,这个新函数里面的一个变量正好是外部 make_adder 的参数。也就是说,外部传递过来的 arg_1 参数已经和 adder 函数绑定到一起了,形成了一个新函数,我们可以把  arg_1 看做新函数的一个配置信息,配置信息不同,函数的功能就不一样了,也就是能得到定制之后的函数.

再看看运行结果,我们发现,虽然 p 和 q 都是 make_adder 生成的,但是因为配置参数不同,后面再执行相同参数的函数后得到了不同的结果。这就是闭包。

示例 2:

def hello_counter(name):
    count = [0]

    def counter():
        count[0] += 1
        print('Hello,', name, ',', str(count[0]) + ' access!')
    return counter


hello = hello_counter('king')
hello()
hello()
hello()

# 执行结果
# Hello, king , 1 access!
# Hello, king , 2 access!
# Hello, king , 3 access!

分析

这个程序比较有趣,我们可以把这个程序看做统计一个函数调用次数的函数。count[0]可以看做一个计数器,每执行一次 hello 函数,count[0] 的值就加 1。也许你会有疑问:为什么不直接写 count 而用一个列表? 这是 python2 的一个bug,如果不用列表的话,会报这样一个错误:UnboundLocalError: local variable 'count' referenced before assignment.

什么意思? 就是说 conut 这个变量你没有定义就直接引用了,我不知道这是个什么东西,程序就崩溃了。于是在 python3 里面引入了一个关键字:nonlocal,这个关键字是干什么的? 就是告诉 python 程序,我的这个 count 变量是在外部定义的,你去外面找吧。然后 python 就去外层函数找,然后就找到了 count=0 这个定义和赋值,程序就能正常执行了。

示例 2 改进:

def hello_counter(name):
    count = 0

    def counter():
        nonlocal count
        count += 1
        print('Hello,', name, ',', str(count) + ' access!')
    return counter


hello = hello_counter('king')
hello()
hello()
hello()

关于这个问题的研究您可以参考:http://linluxiang.iteye.com/blog/789946

示例 3:

def make_bold(fn):
    def wrapped():
        return "<b>" + fn() + "</b>"

    return wrapped


def make_italic(fn):
    def wrapped():
        return "<i>" + fn() + "</i>"

    return wrapped


@make_bold
@make_italic
def hello():
    return "hello world"


print(hello())

# 执行结果
# <b><i>hello world</i></b>

简单分析

怎么样? 这个程序熟悉吗? 这不是传说的的装饰器吗? 对,这就是装饰器,其实,装饰器就是一种闭包,

我们再回想一下装饰器的概念:对函数(参数,返回值 等)进行加工处理,生成一个功能增强版的一个函数。

再看看闭包的概念,这个增强版的函数不就是我们配置之后的函数吗 ?

区别在于,装饰器的参数是一个 函数 ,专门对 函数 进行加工处理。

Python 里面的好多高级功能,比如装饰器,生成器,列表推到,闭包,匿名函数等。

5、探索 装饰器

带参数 的 装饰器

示例代码:

import time


def function_performance_statistics(trace_this=True):
    if trace_this:
        def performance_statistics_delegate(func):
            def counter(*args, **kwargs):
                start = time.perf_counter()
                func(*args, **kwargs)
                end = time.perf_counter()
                print('used time: %d' % (end - start,))

            return counter
    else:
        def performance_statistics_delegate(func):
            return func
    return performance_statistics_delegate


@function_performance_statistics(True)
def add(x, y):
    time.sleep(3)
    print('add result: %d' % (x + y,))


@function_performance_statistics(False)
def mul(x, y=1):
    print('mul result: %d' % (x * y,))


add(1, 1)
mul(10)

上述代码想要实现一个性能分析器,并接收一个参数,来控制性能分析器是否生效,其运行效果如下所示:

add result: 2
used time: 3
mul result: 10

上述代码中装饰器的调用等价于:

import time


def function_performance_statistics(trace_this=True):
    if trace_this:
        def performance_statistics_delegate(func):
            def counter(*args, **kwargs):
                start = time.perf_counter()
                func(*args, **kwargs)
                end = time.perf_counter()
                print('used time: %d' % (end - start,))

            return counter
    else:
        def performance_statistics_delegate(func):
            return func
    return performance_statistics_delegate


@function_performance_statistics(True)
def add(x, y):
    time.sleep(3)
    print('add result: %d' % (x + y,))


@function_performance_statistics(False)
def mul(x, y=1):
    print('mul result: %d' % (x * y,))


add = function_performance_statistics(True)(add(1, 1))  
mul = function_performance_statistics(False)(mul(10))

装饰器的调用顺序

装饰器的调用顺序与使用 @ 语法糖声明的顺序相反,如下所示:

def decorator_a(func):
    print("decorator_a")
    return func


def decorator_b(func):
    print("decorator_b")
    return func


@decorator_a
@decorator_b
def foo():
    print("foo")


foo()

其等价于:

def decorator_a(func):
    print("decorator_a")
    return func


def decorator_b(func):
    print("decorator_b")
    return func


def foo():
    print("foo")


foo = decorator_a(decorator_b(foo))
foo()

通过等价的调用形式我们可以看到,按照 python 的函数求值序列,decorator_b(fun) 会首先被求值,然后将其结果作为输入,传递给 decorator_a,因此其调用顺序与声明顺序相反。

其运行结果如下所示:

decorator_b
decorator_a
foo

调用时机

装饰器很好用,那么它什么时候被调用?性能开销怎么样?会不会有副作用?接下来我们就以几个实例来验证我们的猜想。

首先我们验证一下装饰器的性能开销,代码如下所示:

def decorator_a(func):
    print("decorator_a")
    print('func id: ' + str(id(func)))
    return func


def decorator_b(func):
    print("decorator_b")
    print('func id: ' + str(id(func)))
    return func


print('Begin declare foo with decorators')


@decorator_a
@decorator_b
def foo():
    print("foo")


print('End declare foo with decorators')

print('First call foo')
foo()

print('Second call foo')
foo()

print('Function infos')
print('decorator_a id: ' + str(id(decorator_a)))
print('decorator_b id: ' + str(id(decorator_b)))
print('foo id : ' + str(id(foo)))

运行结果如下:

Begin declare foo with decorators
decorator_b
func id: 1474741780056
decorator_a
func id: 1474741780056
End declare foo with decorators
First call foo
foo
Second call foo
foo
Function infos
decorator_a id: 1474740396104
decorator_b id: 1474740398552
foo id : 1474741780056

在运行结果中:

Begin declare foo with decorators
decorator_b
func id: 1474741780056
decorator_a
func id: 1474741780056
End declare foo with decorators

证实了装饰器的调用时机为: 被装饰对象定义时

而运行结果中的:

First call foo
foo
Second call foo
foo

证实了在相同 .py 文件中,装饰器对所装饰的函数只进行一次装饰,不会每次调用相应函数时都重新装饰,这个很容易理解,因为其本质等价于下面的函数签名重新绑定:

foo = decorator_a(decorator_b(foo))  

对于跨模块的调用,我们编写如下结构的测试代码:

.  
├── common  
│   ├── decorator.py  
│   ├── __init__.py  
│   ├── mod_a  
│   │   ├── fun_a.py  
│   │   └── __init__.py  
│   └── mod_b  
│       ├── fun_b.py  
│       └── __init__.py  
└── test.py  

上述所有模块中的 __init__.py 文件均为: # -*- coding: utf-8 -*-

common/mod_a/fun_a.py

# -*- coding: utf-8 -*-  
# common/mod_a/fun_a.py  


from common.decorator import foo


def fun_a():
    print('in common.mod_a.fun_a.fun_a call foo')    
    foo()

common/mod_b/fun_b.py  

# -*- coding: utf-8 -*-  
# common/mod_b/fun_b.py  

from common.decorator import foo


def fun_b():
    print('in common.mod_b.fun_b.fun_b call foo')    
    foo()

common/decorator.py  

# -*- coding: utf-8 -*-
# common/decorator.py


def decorator_a(func):
    print('init decorator_a')
    return func


@decorator_a
def foo():
    print('function foo')

test.py

# -*- coding: utf-8 -*-
# test.py

from common.mod_a.fun_a import fun_a
from common.mod_b.fun_b import fun_b

fun_a()
fun_b()

上述代码通过创建 common.mod_a 和 common.mod_b 两个子模块,并调用 common.decorator 中的 foo 函数,来测试跨模块时装饰器的工作情况,运行 test.py 的结果如下所示:

init decorator_a  
in common.mod_a.fun_a.fun_a call foo  
function foo  
in common.mod_b.fun_b.fun_b call foo  
function foo  

经过上面的验证,可以看出,对于跨模块的调用,装饰器也只会初始化一次,不过这要归功于 *.pyc,这与本文主题无关,故不详述。

使用 @functools.wraps 

关于装饰器副作用的话题比较大,这不仅仅是装饰器本身的问题,更多的时候是我们设计上的问题,下面给出一个初学装饰器时大家都会遇到的一个问题 —— 丢失函数元信息:

def decorator_a(func):
    def inner(*args, **kwargs):
        res = func(*args, **kwargs)
        return res
    return inner


@decorator_a
def foo():
    """
        foo doc
    :return:
    """
    return 'foo result'


print('foo.__module__: ' + str(foo.__module__))
print('foo.__name__: ' + str(foo.__name__))
print('foo.__doc__: ' + str(foo.__doc__))
print(foo())

运行结果:

foo.__module__: __main__
foo.__name__: inner
foo.__doc__: None
foo result

可以看到,在使用 decorator_a 对 foo 函数进行装饰后,foo 的元信息会丢失,解决方案参见: https://docs.python.org/zh-cn/3.11/library/functools.html#functools.wraps

多个装饰器运行期行为

前面已经讲解过装饰器的调用顺序和调用时机,但是被多个装饰器装饰的函数,其运行期行为还是有一些细节需要说明的,而且很可能其行为会让你感到惊讶,下面时一个实例:

def tracer(msg):
    print("[TRACE] %s" % msg)


def logger(func):
    tracer("logger")

    def inner(username, password):
        tracer("inner")
        print("call %s" % func.__name__)
        return func(username, password)
    return inner


def login_debug_helper(show_debug_info=False):
    tracer("login_debug_helper")

    def proxy_fun(func):
        tracer("proxy_fun")

        def delegate_fun(username, password):
            tracer("delegate_fun")
            if show_debug_info:
                print(f"username:{username}\npassword:{password}")
            return func(username, password)

        return delegate_fun

    return proxy_fun


print('Declaring login_a')


@logger
@login_debug_helper(show_debug_info=True)
def login_a(username, password):
    tracer("login_a")
    print("do some login authentication")
    return True


print('Call login_a')
login_a("mdl", "pwd")

大家先来看一下运行结果,看看是不是跟自己想象中的一致:

Declaring login_a
[TRACE] login_debug_helper
[TRACE] proxy_fun
[TRACE] logger
Call login_a
[TRACE] inner
call delegate_fun
[TRACE] delegate_fun
username:mdl
password:pwd
[TRACE] login_a
do some login authentication

首先,装饰器初始化时的调用顺序与我们前面讲解的一致,如下:

Declaring login_a
[TRACE] login_debug_helper
[TRACE] proxy_fun
[TRACE] logger

然而,接下来,来自 logger 装饰器中的 inner 函数首先被执行,然后才是login_debug_helper 返回的 proxy_fun 中的 delegate_fun 函数。各位读者发现了吗,运行期执行login_a 函数的时候,装饰器中返回的函数的执行顺序是相反的,难道是我们前面讲解的例子有错误吗?其实,如果大家的认为运行期调用顺序应该与装饰器初始化阶段的顺序一致的话,那说明大家没有看透这段代码的调用流程,下面我来为大家分析一下。

def login_debug_helper(show_debug_info=False):
    tracer("login_debug_helper")

    def proxy_fun(func):
        tracer("proxy_fun")

        def delegate_fun(username, password):
            tracer("delegate_fun")
            if show_debug_info:
                print(f"username:{username}\npassword:{password}")
            return func(username, password)

        return delegate_fun

    return proxy_fun

当装饰器 login_debug_helper 被调用时,其等价于:

gin_debug_helper(show_debug_info=True)(login_a)('mdl', 'pwd')  

对于只有 login_debug_helper 的情况,现在就应该是执行完 login_a 输出结果的时刻了,但是如果现在在加上 logger 装饰器的话,那么这个 login_debug_helper(show_debug_info=True)(login_a)('mdl', 'pwd') 就被延迟执行,而将 login_debug_helper(show_debug_info=True)(login_a) 作为参数传递给 logger,我们令 login_tmp = login_debug_helper(show_debug_info=True)(login_a),则调用过程等价于:

login_tmp = login_debug_helper(show_debug_info=True)(login_a)  
login_a = logger(login_tmp)  
login_a('mdl', 'pwd')  

相信大家看过上面的等价变换后,已经明白问题出在哪里了,如果你还没有明白,我强烈建议你把这个例子自己敲一遍,并尝试用自己的方式进行化简,逐步得出结论。

装饰器合并

多个装饰器合并成一个装饰器。

为什么要合并多个装饰器?

因为堆叠装饰器虽然说不是不行,但是随着项目越来越大,装饰器越堆越多,我们希望将一些关联性强的装饰器合并在一起,而不是像下面的代码一样,搞得像叠罗汉一样

# 公司业务代码其中一个接口 【flask-restplus】
class Goods(Resource)
    @staticmethod
    @document.request_goods_payload
    @document.response_goods_id_success
    @document.response_goods_time_format_error
    @document.response_goods_name_format_error
    @document.response_goods_weight_format_error
    @document.response_goods_volume_format_error
    @document.response_goods_expect_price_format_error
    @document.response_goods_description_format_error
    @document.response_goods_same_address_error
    @document.response_goods_require_vehicle_error
    @document.response_add_goods_duplicate
    @document.response_unauth_restriction
    @document.response_auth_restriction
    @catch_exception_log()
    @filters.Goods.return_goods_id(goods_id=int, is_dispatch=int, is_prepaid=int)
    @dispatchs.Goods.start(goods_id=int, is_dispatch=int, is_prepaid=int)
    @operations.Goods.add_goods(user_id=int, payload=dict, app_version=str)
    @config_operation.ConfigOperation.get_feed_config(user_id=int, payload=dict, app_version=str)
    @operations.Goods.check_duplicate_goods(user_id=int, payload=dict, app_version=str)
    @operations.Goods.check_add_goods_restriction(user_id=int, payload=dict, app_version=str)
    @verifies.Goods.check_goods_payload_fields(user_id=int, app_version=str, payload=dict, code=str, channel=str)
    @verifies.Token.token(token=str, app_version=str, payload=dict, code=str, channel=str)
    def post():
        """ 预约回头车【需登录】 """
        pass

解决叠罗汉得方法有三种

  • 方法一:通过参数的里的元组(包含很多个装饰器)
  • 方法二:用指定的多个装饰器将调用链写好先,缺点是每次要加多一个装饰器调用,需要改源代码
  • 方法三:先通过函数调用获得一个装饰器函数先,然后再装饰目标函数
import functools


def deco1(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("deco1")
        return func(*args, **kwargs)
    return wrapper


def deco2(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("deco2")
        return func(*args, **kwargs)
    return wrapper


# 方法一:通过参数的里的元组(包含很多个装饰器)
def composed(*decs, is_reversed=False):
    def deco(f):
        if is_reversed:
            for dec in reversed(decs):
                f = dec(f)
        else:
            for dec in decs:
                f = dec(f)
        return f
    return deco


# 方法二:用指定的多个装饰器将调用链写好先,缺点是每次要加多一个装饰器调用,需要改源代码
def mutiple_deco(func):
    return deco2(deco1(func))

# 方法三:先通过函数调用获得一个装饰器函数先,然后再装饰目标函数
def generate_deco(f, g):
    return lambda x: f(g(x))


# 原来的装饰器装饰方式
@deco2
@deco1
def add(a: int, b: int) -> int:
    return a + b


@composed(deco1, deco2, is_reversed=False)
def add_method1(a: int, b: int) -> int:
    return a + b


@mutiple_deco
def add_method2(a: int, b: int) -> int:
    return a + b


combined_deco = generate_deco(deco2, deco1)

@combined_deco
def add_method3(a: int, b: int) -> int:
    return a + b


if __name__ == "__main__":
    r = add(10, 1)
    print("结果是:", r)

    r = add_method1(10, 2)
    print("结果是:", r)

    r = add_method2(10, 3)
    print("结果是:", r)

    r = add_method3(10, 4)
    print("结果是:", r)

方法一,这个方法更可控,并且可以自己调整装饰器的装饰顺序

最终的效果如下:

# 【flask-restplus】
class ApiClass(Resource)
    @staticmethod
    @multi_deco(DOC1, DOC2...)
    @multi_deco(FILTER1, FILTER2...)
    @multi_deco(DISPATCH1, DISPATCH2...)
    @multi_deco(OPERATION1, OPERATION2...)
    @multi_deco(VERIFY1, VERIFY2...)
    def post():
        """ 接口文档 """
        pass

示例 2:

如果有多个装饰器,你可以将它们组合成一个单独的装饰器,以便更方便地使用。你可以使用以下语法将多个装饰器组合成一个:

@decorator1
@decorator2
@decorator3
def function():
    pass

这里的decorator1decorator2decorator3分别是要组合的装饰器函数,它们会按照从上到下的顺序依次被调用,最终返回的函数对象会替换原来的function函数。

如果你想将多个装饰器合并成一个装饰器函数,可以按照以下步骤操作:

1. 定义一个新的装饰器函数,将需要合并的多个装饰器作为参数传入:

def combined_decorator(decorator1, decorator2, decorator3):
    def decorator(func):
        return decorator1(decorator2(decorator3(func)))
    return decorator

这里的decorator1decorator2decorator3是需要合并的多个装饰器函数,combined_decorator函数接受这些函数作为参数,并返回一个新的装饰器函数decorator

2. 将需要合并的装饰器函数作为参数传入combined_decorator函数,并将返回的新装饰器函数应用于目标函数:

@combined_decorator(decorator1, decorator2, decorator3)
def my_function():
    pass

这里的decorator1decorator2decorator3是需要合并的多个装饰器函数,my_function是目标函数。使用@语法将新装饰器函数应用于my_function函数。

这样,就可以将多个装饰器函数组合成一个单独的装饰器函数,更方便的使用。

6、一些 decorator 的示例

给函数调用做缓存

这个示例太经典了,整个网上都用这个例子做decorator的经典范例。

from functools import wraps
def memo(fn):
    cache = {}
    miss = object()
 
    @wraps(fn)
    def wrapper(*args):
        result = cache.get(args, miss)
        if result is miss:
            result = fn(*args)
            cache[args] = result
        return result
 
    return wrapper
 
@memo
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

上面这个例子中,是一个斐波拉契数例的递归算法。我们知道,这个递归是相当没有效率的,因为会重复调用。比如:我们要计算fib(5),于是其分解成fib(4) + fib(3),而fib(4)分解成fib(3)+fib(2),fib(3)又分解成fib(2)+fib(1)…… 你可看到,基本上来说,fib(3), fib(2), fib(1)在整个递归过程中被调用了两次。

而我们用decorator,在调用函数前查询一下缓存,如果没有才调用了,有了就从缓存中返回值。一下子,这个递归从二叉树式的递归成了线性的递归。

Profiler 的例子

这个例子没什么高深的,就是实用一些。

import cProfile, pstats, StringIO
 
def profiler(func):
    def wrapper(*args, **kwargs):
        datafn = func.__name__ + ".profile" # Name the data file
        prof = cProfile.Profile()
        retval = prof.runcall(func, *args, **kwargs)
        #prof.dump_stats(datafn)
        s = StringIO.StringIO()
        sortby = 'cumulative'
        ps = pstats.Stats(prof, stream=s).sort_stats(sortby)
        ps.print_stats()
        print s.getvalue()
        return retval
 
    return wrapper

注册回调函数

下面这个示例展示了通过URL的路由来调用相关注册的函数示例:

class MyApp():
    def __init__(self):
        self.func_map = {}
 
    def register(self, name):
        def func_wrapper(func):
            self.func_map[name] = func
            return func
        return func_wrapper
 
    def call_method(self, name=None):
        func = self.func_map.get(name, None)
        if func is None:
            raise Exception("No function registered against - " + str(name))
        return func()
 
app = MyApp()
 
@app.register('/')
def main_page_func():
    return "This is the main page."
 
@app.register('/next_page')
def next_page_func():
    return "This is the next page."
 
print app.call_method('/')
print app.call_method('/next_page')

注意:
1)上面这个示例中,用类的实例来做decorator。
2)decorator类中没有__call__(),但是wrapper返回了原函数。所以,原函数没有发生任何变化。

给函数打日志

下面这个示例演示了一个logger的decorator,这个decorator输出了函数名,参数,返回值,和运行时间。

from functools import wraps
def logger(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        ts = time.time()
        result = fn(*args, **kwargs)
        te = time.time()
        print "function      = {0}".format(fn.__name__)
        print "    arguments = {0} {1}".format(args, kwargs)
        print "    return    = {0}".format(result)
        print "    time      = %.6f sec" % (te-ts)
        return result
    return wrapper
 
@logger
def multipy(x, y):
    return x * y
 
@logger
def sum_num(n):
    s = 0
    for i in xrange(n+1):
        s += i
    return s
 
print multipy(2, 10)
print sum_num(100)
print sum_num(10000000)

上面那个打日志还是有点粗糙,让我们看一个更好一点的(带log level参数的):

import inspect
def get_line_number():
    return inspect.currentframe().f_back.f_back.f_lineno
 
def logger(loglevel):
    def log_decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            ts = time.time()
            result = fn(*args, **kwargs)
            te = time.time()
            print "function   = " + fn.__name__,
            print "    arguments = {0} {1}".format(args, kwargs)
            print "    return    = {0}".format(result)
            print "    time      = %.6f sec" % (te-ts)
            if (loglevel == 'debug'):
                print "    called_from_line : " + str(get_line_number())
            return result
        return wrapper
    return log_decorator

但是,上面这个带log level参数的有两具不好的地方,
1) loglevel不是debug的时候,还是要计算函数调用的时间。
2) 不同level的要写在一起,不易读。

我们再接着改进:

import inspect
 
def advance_logger(loglevel):
 
    def get_line_number():
        return inspect.currentframe().f_back.f_back.f_lineno
 
    def _basic_log(fn, result, *args, **kwargs):
        print "function   = " + fn.__name__,
        print "    arguments = {0} {1}".format(args, kwargs)
        print "    return    = {0}".format(result)
 
    def info_log_decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            result = fn(*args, **kwargs)
            _basic_log(fn, result, args, kwargs)
        return wrapper
 
    def debug_log_decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            ts = time.time()
            result = fn(*args, **kwargs)
            te = time.time()
            _basic_log(fn, result, args, kwargs)
            print "    time      = %.6f sec" % (te-ts)
            print "    called_from_line : " + str(get_line_number())
        return wrapper
 
    if loglevel is "debug":
        return debug_log_decorator
    else:
        return info_log_decorator

你可以看到两点,
1)我们分了两个log level,一个是info的,一个是debug的,然后我们在外尾根据不同的参数返回不同的decorator。
2)我们把info和debug中的相同的代码抽到了一个叫_basic_log的函数里,DRY原则。

一个 MySQL 的 Decorator

下面这个decorator是我在工作中用到的代码,我简化了一下,把DB连接池的代码去掉了,这样能简单点,方便阅读。

import umysql
from functools import wraps
 
class Configuraion:
    def __init__(self, env):
        if env == "Prod":
            self.host    = "coolshell.cn"
            self.port    = 3306
            self.db      = "coolshell"
            self.user    = "coolshell"
            self.passwd  = "fuckgfw"
        elif env == "Test":
            self.host   = 'localhost'
            self.port   = 3300
            self.user   = 'coolshell'
            self.db     = 'coolshell'
            self.passwd = 'fuckgfw'
 
def mysql(sql):
 
    _conf = Configuraion(env="Prod")
 
    def on_sql_error(err):
        print err
        sys.exit(-1)
 
    def handle_sql_result(rs):
        if rs.rows > 0:
            fieldnames = [f[0] for f in rs.fields]
            return [dict(zip(fieldnames, r)) for r in rs.rows]
        else:
            return []
 
    def decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            mysqlconn = umysql.Connection()
            mysqlconn.settimeout(5)
            mysqlconn.connect(_conf.host, _conf.port, _conf.user, \
                              _conf.passwd, _conf.db, True, 'utf8')
            try:
                rs = mysqlconn.query(sql, {})
            except umysql.Error as e:
                on_sql_error(e)
 
            data = handle_sql_result(rs)
            kwargs["data"] = data
            result = fn(*args, **kwargs)
            mysqlconn.close()
            return result
        return wrapper
 
    return decorator
 
@mysql(sql = "select * from coolshell" )
def get_coolshell(data):
    ... ...
    ... ..

线程异步

下面量个非常简单的异步执行的decorator,注意,异步处理并不简单,下面只是一个示例。

from threading import Thread
from functools import wraps
 
def async(func):
    @wraps(func)
    def async_func(*args, **kwargs):
        func_hl = Thread(target = func, args = args, kwargs = kwargs)
        func_hl.start()
        return func_hl
 
    return async_func
 
if __name__ == '__main__':
    from time import sleep
 
    @async
    def print_somedata():
        print 'starting print_somedata'
        sleep(2)
        print 'print_somedata: 2 sec passed'
        sleep(2)
        print 'print_somedata: 2 sec passed'
        sleep(2)
        print 'finished print_somedata'
 
    def main():
        print_somedata()
        print 'back in main'
        print_somedata()
        print 'back in main'
 
    main()

超时

给函数添加超时功能,这个功能在编写外部API调用 、网络爬虫、数据库查询的时候特别有用。

timeout装饰器的代码如下:

# coding=utf-8
# 测试utf-8编码
import sys

reload(sys)
sys.setdefaultencoding('utf-8')

import signal, functools


class TimeoutError(Exception): pass


def timeout(seconds, error_message="Timeout Error: the cmd 30s have not finished."):
    def decorated(func):
        result = ""

        def _handle_timeout(signum, frame):
            global result
            result = error_message
            raise TimeoutError(error_message)

        def wrapper(*args, **kwargs):
            global result
            signal.signal(signal.SIGALRM, _handle_timeout)
            signal.alarm(seconds)

            try:
                result = func(*args, **kwargs)
            finally:
                signal.alarm(0)
                return result
            return result

        return functools.wraps(func)(wrapper)

    return decorated


@timeout(2)  # 限定下面的slowfunc函数如果在5s内不返回就强制抛TimeoutError Exception结束
def slowfunc(sleep_time):
    a = 1
    import time
    time.sleep(sleep_time)
    return a


# slowfunc(3) #sleep 3秒,正常返回 没有异常


print slowfunc(11)  # 被终止

使用第三方库:func_timeout,安装:pip install func_timeout

import time
import requests
from func_timeout import func_timeout, func_set_timeout, FunctionTimedOut


@func_set_timeout(1)
def func_1():
    while True:
        print('hello world')
        time.sleep(1)


@func_set_timeout(1)
def func_2():
    url = 'https://www.google.com'
    print("开始请求")
    r = requests.get(url)


def func_3():
    def my_function(*args, **kwargs):
        print(args)
    try:
        result = func_timeout(3, my_function, args=('arg1', 'arg2'))
        print(result)
    except FunctionTimedOut:
        print("Function took too long to complete")
    pass


def main():
    try:
        func_1()
    except FunctionTimedOut:
        pass
    try:
        func_2()
    except FunctionTimedOut:
        pass
    func_3()


if __name__ == '__main__':
    main()

执行时间 (装饰器、或者 timeit)

使用 timeit

文档:https://docs.python.org/zh-cn/3/library/timeit.html

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)

  • stmt: 需要执行的语句代码。默认是 pass

  • setup: 测量 stmt 代码执行时间之前,要运行的代码,主要作用是设置一些必要的变量。默认pass

  • timer: 要使用的定时器功能。默认值是平台的默认计时器(通常是time.perf_counter)。

  • number: 执行的次数. 默认 1,000,000.

  • globals: 表示将在其中执行代码的全局命名空间的字典。这是您可以传递函数和任何所需变量的地方

示例:

import timeit

def my_function(a, b):
    # Your code here
    result = a + b
    return result

# Define parameters for the function
a_value = 10
b_value = 20

# Using timeit to measure the execution time
execution_time = timeit.timeit(
    "my_function(a_value, b_value)", 
    globals=globals(), number=1000
)

print(f"Execution time: {execution_time} seconds")
import timeit

def my_function(a, b):
    # Your code here
    result = a + b
    return result

# Define parameters for the function
a_value = 10
b_value = 20

# Using the setup parameter for additional setup
setup_code = '''
from __main__ import my_function
a_value = 10
b_value = 20
'''

execution_time = timeit.timeit(
    "my_function(a_value, b_value)", 
    setup=setup_code, number=1000
)

print(f"Execution time: {execution_time} seconds")
import timeit

code_to_measure = '''
# Your code here
result = sum(range(1000))
'''
execution_time = timeit.timeit(code_to_measure, number=1000)
print(f"Execution time: {execution_time} seconds")

def f(x):
    return x ** 2
def g(x):
    return x ** 4
def h(x):
    return x ** 8

print(timeit.timeit('[func(42) for func in (f,g,h)]', globals=globals()))

命令行执行

python -m timeit -s "from my_module import my_function" "my_function()"

失败 重试

安装:pip install retry2

使用方法:https://pypi.org/project/retry2/

Trace 函数

有时候出于演示目的或者调试目的,我们需要程序运行的时候打印出每一步的运行顺序 和调用逻辑。类似写bash的时候的bash -x调试功能,然后Python解释器并没有 内置这个时分有用的功能,那么我们就“自己动手,丰衣足食”。

Trace装饰器的代码如下:

# coding=utf-8
# 测试utf-8编码
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

import sys,os,linecache
def trace(f):
  def globaltrace(frame, why, arg):
    if why == "call": return localtrace
    return None
  def localtrace(frame=1, why=2, arg=4):
    if why == "line":
      # record the file name and line number of every trace
      filename = frame.f_code.co_filename
      lineno = frame.f_lineno
      bname = os.path.basename(filename)
      print "{}({}): {}".format(  bname,
                    lineno,
                    linecache.getline(filename, lineno)),
    return localtrace
  def _f(*args, **kwds):
    sys.settrace(globaltrace)
    result = f(*args, **kwds)
    sys.settrace(None)
    return result
  return _f

@trace
def xxx():
  a=1
  print a
  print 22
  print 333

xxx() #调用

#######################################
C:\Python27\python.exe F:/sourceDemo/flask/study/com.test.bj/t2.py
t2.py(31):   a=1
t2.py(32):   print a
1
t2.py(33):   print 22
22
t2.py(34):   print 333
333

Process finished with exit code 0

单例模式

示例代码:

# coding=utf-8
# 测试utf-8编码
# 单例装饰器
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

# 使用装饰器实现简单的单例模式
def singleton(cls):
    instances = dict()  # 初始为空
    def _singleton(*args, **kwargs):
        if cls not in instances:  #如果不存在, 则创建并放入字典
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return _singleton

@singleton
class Test(object):
    pass
if __name__ == '__main__':
    t1 = Test()
    t2 = Test()
    # 两者具有相同的地址
    print t1
    print t2

LRUCache

下面要分享的这个LRUCache不是我做的,是github上的一个库,我们在实际环境中有用到。

先来说下这个概念,cache的意思就是缓存,LRU就是Least Recently Used,即最近最少使用,是一种内存管理算法。总结来说这就是一种缓存方法,基于时间和容量。

一般在简单的python程序中,遇到需要处理缓存的情况时最简单的方式,声明一个全局的dict就能解决(在python中应尽量避免使用全局变量)。但是只是简单情况,这种情况会带来的问题就是内存泄漏,因为可能会出现一直不命中的情况。

由此导致的一个需求就是,你要设定这个dict的最大容量,防止发生泄漏。但仅仅是设定最大容量是不够的,设想当你的dict变量已被占满,还是没有命中,该如何处理。

这时就需要加一个失效时间了。如果在指定失效期内没有使用到该缓存,则删除。

综述上面的需求和功能就是LRUCache干的事了。不过这份代码做了更进一层的封装,可以让你直接把缓存功能做为一个装饰器来用。具体实现可以去参考代码,别人实现之后看起来并不复杂 :)

代码: https://github.com/the5fire/Python-LRU-cache/blob/master/lru.py

从python3.2开始内置在functools了lru_cache的功能,说明这个需求是很普遍的。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值