Python装饰器学习(九步入门):http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html
浅谈Python装饰器:https://blog.csdn.net/mdl13412/article/details/22608283
Python装饰器与面向切面编程:http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html
一些更加实用的 Python 装饰器示例:https://sikasjc.github.io/2018/09/29/somedecorator/
Python装饰器高级版—Python类内定义装饰器并传递self参数:https://blog.51cto.com/yishi/2354752
Python 装饰器学习(九步入门)
这是在 Python学习小组上介绍的内容,现学现卖、多练习是好的学习方式。
第一步:最简单的函数,准备附加额外功能
示例代码:
- # -*- coding:gbk -*-
- '''示例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.
- """
第二步:使用装饰函数在函数执行前和执行后分别附加额外功能
示例代码:
- # -*- coding:gbk -*-
- '''示例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 没有使用内嵌函数,可以看到第一次执行可以进入到装饰函数,但是第二次不会进入装饰函数
- # -*- coding:gbk -*-
- '''示例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。使用内嵌包装函数来确保每次新函数都被调用
- # -*- coding:gbk -*-
- '''示例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()
第五步:对 带参数的函数 进行装饰( 被装饰的函数带参数 )
示例代码:
- # -*- coding:gbk -*-
- '''示例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)
第六步:对 参数数量不确定 的函数进行装饰
示例代码:
- # -*- coding:gbk -*-
- '''示例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
- """
第七步:让 装饰器 带 参数
示例代码:
- # -*- coding:gbk -*-
- '''示例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()
第八步:让 装饰器 带 类 参数
示例代码:
- # -*- coding:gbk -*-
- '''示例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
- # -*- coding:gbk -*-
-
-
- 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
测试代码:
- # -*- coding:gbk -*-
-
- """
- 示例 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
- """
下面是参考资料,当初有不少地方没看明白,真正练习后才明白些:
1. Python装饰器学习:http://blog.csdn.net/thy38/article/details/4471421
2. Python装饰器与面向切面编程:http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html
3. Python装饰器的理解:http://apps.hi.baidu.com/share/detail/17572338
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():
- print("2019-12-31")
-
-
- print(func.__name__, func.__doc__)
-
- # 加上 @wraps(src_func) 输出为func None
- # 不加 输出为 wrapper None
一:函数 装饰 函数
- 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)
二:函数 装饰 类
示例:
- 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()
示例:
创建单例(Singletons)
单例是一个只有一个实例的类。Python中有几种常用的单例,包括None
、True
和False
。事实上,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中,单例类的使用并不像在其他语言中那样频繁。单例的效果通常在模块中作为全局变量更好地实现。
三:类 装饰 函数、方法
类 装饰 函数
定义一个类装饰器,装饰函数,默认调用 __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)
类 装饰 方法
定义一个类装饰器,装饰类中的函数,默认调用__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)
四:类 装饰 类
- 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()
函数 装饰 类中的方法
示例代码 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()
类中的方法 装饰 方法 和 函数
- 方法:类中的成员函数叫做 方法
- 函数:不在类中的函数,即普通函数叫做 函数
类中的方法 装饰 函数
在类里面定义个函数,用来装饰其它函数,严格意义上说不属于类装饰器。
- 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,说明修改完成
前置知识
闭 包
javascript 闭包:https://www.runoob.com/js/js-function-closures.html
闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰。直观的说:就是形成一个不销毁的栈环境。
闭包在维基百科上的定义如下: 在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法:认为闭包是由函数和与其相关的引用环境组合而成的实体。
下面给出一个使用闭包实现的 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。
什么是闭包?
内部函数对外部函数作用域里变量的引用(非全局变量),则称内部函数为闭包。
简单说,闭包就是根据不同的配置信息得到不同的结果
再来看看专业的解释:闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。
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 里面的好多高级功能,比如装饰器,生成器,列表推到,闭包,匿名函数等。
探索 装饰器
定义
- 点击打开装饰器在维基百科上的定义链接:http://en.wikipedia.org/wiki/Python_syntax_and_semantics#Decorators
- 如下: A decorator is any callable Python object that is used to modify a function, method or class definition.
基本语法
- @bar
- def foo():
- print("foo")
其等价于:
- def foo():
- print("foo")
-
-
- foo = bar(foo)
无参数 装饰器
- def foo(func):
- print('decorator foo')
- return func
-
-
- @foo
- def bar():
- print('bar')
-
-
- bar()
foo 函数被用作装饰器,其本身接收一个函数对象作为参数,然后做一些工作后,返回接收的参数,供外界调用。
注意:时刻牢记 @foo 只是一个语法糖,其本质是 foo = bar(foo)
带参数 的 装饰器
示例代码:
- 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 bar(dummy):
- print('bar')
-
-
- def inject(cls):
- cls.bar = bar
- return cls
-
-
- @inject
- class Foo(object):
- pass
-
-
- foo = Foo()
- foo.bar()
上述代码的 inject 装饰器为类动态的添加一个 bar 方法,因为类在调用非静态方法的时候会传进一个self 指针,因此 bar 的第一个参数我们简单的忽略即可,
运行结果如下:bar
类 装饰器
类装饰器 相比 函数装饰器,具有灵活度大,高内聚、封装性等优点。其实现起来主要是靠类内部的 __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
内置装饰器
Python 中内置的装饰器有三个: staticmethod、classmethod 和 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
调用顺序
装饰器的调用顺序与使用 @ 语法糖声明的顺序相反,如下所示:
- 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,这与本文主题无关,故不详述。
装饰器副作用
关于装饰器副作用的话题比较大,这不仅仅是装饰器本身的问题,更多的时候是我们设计上的问题,
下面给出一个初学装饰器时大家都会遇到的一个问题 —— 丢失函数元信息:
- 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 的元信息会丢失,解决方案参见: 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')
相信大家看过上面的等价变换后,已经明白问题出在哪里了,如果你还没有明白,我强烈建议你把这个例子自己敲一遍,并尝试用自己的方式进行化简,逐步得出结论。
本文主要讲解原理性的东西,具体的实例可以参考下面的链接:Python装饰器实例:调用参数合法性验证
Understanding Python Decorators in 12 Easy Steps
Decorators and Functional Python
Meta-matters: Using decorators for better Python programming
理解 Python 中的装饰器
From:https://www.cnblogs.com/rollenholt/archive/2012/05/02/2479833.html
文章先由 stackoverflow上面的一个问题引起吧,如果使用如下的代码:
- @makebold
- @makeitalic
- def say():
- return "Hello"
打印出如下的输出:
<b><i>Hello<i></b>
你会怎么做?最后给出的答案是:
- 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的装饰器。英文讨论参考Here。
装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
1.1. 需求是怎么来的 ?
装饰器的定义很是抽象,我们来看一个小例子。
- def foo():
- print('in foo()')
-
-
- foo()
这是一个很无聊的函数没错。但是突然有一个更无聊的人,我们称呼他为B君,说我想看看执行这个函数用了多长时间,好吧,那么我们可以这样做:
- import time
-
-
- def foo():
- start = time.clock()
- print('in foo()')
- end = time.clock()
- print('used:', end - start)
-
-
- foo()
很好,功能看起来无懈可击。可是蛋疼的B君此刻突然不想看这个函数了,他对另一个叫foo2的函数产生了更浓厚的兴趣。
怎么办呢?如果把以上新增加的代码复制到foo2里,这就犯了大忌了~复制什么的难道不是最讨厌了么!而且,如果B君继续看了其他的函数呢?
1.2. 以不变应万变,是变也
还记得吗,函数在Python中是一等公民,那么我们可以考虑重新定义一个函数timeit,将foo的引用传递给他,然后在timeit中调用foo并进行计时,这样,我们就达到了不改动foo定义的目的,而且,不论B君看了多少个函数,我们都不用去修改函数定义了!
- import time
-
-
- def foo():
- print('in foo()')
-
-
- def timeit(func):
- start = time.clock()
- func()
- end = time.clock()
- print('used:', end - start)
-
-
- timeit(foo)
看起来逻辑上并没有问题,一切都很美好并且运作正常!……等等,我们似乎修改了调用部分的代码。原本我们是这样调用的:foo(),修改以后变成了:timeit(foo)。这样的话,如果foo在N处都被调用了,你就不得不去修改这N处的代码。或者更极端的,考虑其中某处调用的代码无法修改这个情况,比如:这个函数是你交给别人使用的。
1.3. 最大限度地少改动!
既然如此,我们就来想想办法不修改调用的代码;如果不修改调用代码,也就意味着调用foo()需要产生调用timeit(foo)的效果。我们可以想到将timeit赋值给foo,但是timeit似乎带有一个参数……想办法把参数统一吧!如果timeit(foo)不是直接产生调用效果,而是返回一个与foo参数列表一致的函数的话……就很好办了,将timeit(foo)的返回值赋值给foo,然后,调用foo()的代码完全不用修改!
- # -*- coding: UTF-8 -*-
- import time
-
-
- def foo():
- print('in foo()')
-
-
- # 定义一个计时器,传入一个,并返回另一个附加了计时功能的方法
- def timeit(func):
- # 定义一个内嵌的包装函数,给传入的函数加上计时功能的包装
- def wrapper():
- start = time.perf_counter()
- func()
- end = time.perf_counter()
- print('used:', end - start)
-
- # 将包装后的函数返回
- return wrapper
-
-
- foo = timeit(foo)
- foo()
这样,一个简易的计时器就做好了!我们只需要在定义foo以后调用foo之前,加上foo = timeit(foo),就可以达到计时的目的,这也就是装饰器的概念,看起来像是foo被timeit装饰了。在在这个例子中,函数进入和退出时需要计时,这被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。与传统编程习惯的从上往下执行方式相比较而言,像是在函数执行的流程中横向地插入了一段逻辑。在特定的业务领域里,能减少大量重复代码。面向切面编程还有相当多的术语,这里就不多做介绍,感兴趣的话可以去找找相关的资料。
这个例子仅用于演示,并没有考虑foo带有参数和有返回值的情况,完善它的重任就交给你了 :)
上面这段代码看起来似乎已经不能再精简了,Python于是提供了一个语法糖来降低字符输入量。
- import time
-
-
- def timeit(func):
- def wrapper():
- start = time.perf_counter()
- func()
- end = time.perf_counter()
- print('used:', end - start)
-
- return wrapper
-
-
- @timeit
- def foo():
- print('in foo()')
-
-
- foo()
重点关注第11行的@timeit,在定义上加上这一行与另外写foo = timeit(foo)完全等价
千万不要以为@有另外的魔力。除了字符输入少了一些,还有一个额外的好处:这样看上去更有装饰器的感觉。
-------------------
Python中函数也是被视为对象(Python 中一切 皆 对象)
要理解python的装饰器,我们首先必须明白在Python中函数也是被视为对象。这一点很重要。先看一个例子:
- def shout(word="yes"):
- return word.capitalize() + " !"
-
-
- print(shout())
- # 输出 : 'Yes !'
-
- # 作为一个对象,你可以把函数赋给任何其他对象变量
- scream = shout
-
- # 注意我们没有使用圆括号,因为我们不是在调用函数
- # 我们把函数shout赋给scream,也就是说你可以通过scream调用shout
- print(scream())
- # 输出 : 'Yes !'
-
- # 还有,你可以删除旧的名字shout,但是你仍然可以通过scream来访问该函数
- del shout
- try:
- print(shout())
- except BaseException as e:
- print(e)
- # 输出 : "name 'shout' is not defined"
-
- print(scream())
- # 输出 : 'Yes !'
我们暂且把这个话题放旁边,我们先看看 python 另外一个很有意思的属性:可以在函数中定义函数:
- def talk():
- # 你可以在talk中定义另外一个函数
- def whisper(word="yes"):
- return word.lower() + "..."
-
- # ... 并且立马使用它
- print(whisper())
-
-
- # 你每次调用'talk',定义在talk里面的whisper同样也会被调用
- talk()
- # 输出 :
- # yes...
-
- # 但是"whisper" 不会单独存在:
- try:
- print(whisper())
- except BaseException as e:
- print(e)
- # 输出 : "name 'whisper' is not defined"*
函数引用
从以上两个例子我们可以得出,函数既然作为一个对象,因此:
- 1. 其可以被赋给其他变量
- 2. 其可以被定义在另外一个函数内
这也就是说,函数可以返回一个函数,看下面的例子:
- def get_talk(type="shout"):
- # 我们定义另外一个函数
- def shout(word="yes"):
- return word.capitalize() + " !"
-
- def whisper(word="yes"):
- return word.lower() + "..."
-
- # 然后我们返回其中一个
- if type == "shout":
- # 我们没有使用(),因为我们不是在调用该函数
- # 我们是在返回该函数
- return shout
- else:
- return whisper
-
-
- # 然后怎么使用呢 ?
- # 把该函数赋予某个变量
- talk = get_talk()
-
- # 这里你可以看到talk其实是一个函数对象:
- print(talk)
- # 输出 : <function shout at 0xb7ea817c>
-
- # 该对象由函数返回的其中一个对象:
- print(talk())
-
- # 或者你可以直接如下调用 :
- print(get_talk("whisper")())
- # 输出 : yes...
还有,既然可以返回一个函数,我们可以把它作为参数传递给函数:
- def do_something_before(func):
- print("I do something before then I call the function you gave me")
- print(func())
-
-
- do_something_before(scream)
- # 输出 :
- # I do something before then I call the function you gave me
- # Yes !
这里你已经足够能理解装饰器了,其他它可被视为封装器。也就是说,它能够让你在装饰前后执行代码而无须改变函数本身内容。
手工装饰
那么如何进行手动装饰呢?
- # 装饰器是一个函数,而其参数为另外一个函数
- def my_shiny_new_decorator(a_function_to_decorate):
- # 在内部定义了另外一个函数:一个封装器。
- # 这个函数将原始函数进行封装,所以你可以在它之前或者之后执行一些代码
- def the_wrapper_around_the_original_function():
- # 放一些你希望在真正函数执行前的一些代码
- print("Before the function runs")
-
- # 执行原始函数
- a_function_to_decorate()
-
- # 放一些你希望在原始函数执行后的一些代码
- print("After the function runs")
-
- # 在此刻,"a_function_to_decrorate"还没有被执行,我们返回了创建的封装函数
- # 封装器包含了函数以及其前后执行的代码,其已经准备完毕
- return the_wrapper_around_the_original_function
-
-
- # 现在想象下,你创建了一个你永远也不远再次接触的函数
- def a_stand_alone_function():
- print("I am a stand alone function, don't you dare modify me")
-
-
- a_stand_alone_function()
- # 输出: I am a stand alone function, don't you dare modify me
-
- # 好了,你可以封装它实现行为的扩展。可以简单的把它丢给装饰器
- # 装饰器将动态地把它和你要的代码封装起来,并且返回一个新的可用的函数。
- a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
- a_stand_alone_function_decorated()
- # 输出 :
- # Before the function runs
- # I am a stand alone function, don't you dare modify me
- # After the function runs
-
- # 现在你也许要求当每次调用a_stand_alone_function时,
- # 实际调用却是a_stand_alone_function_decorated。
- # 实现也很简单,可以用my_shiny_new_decorator来给a_stand_alone_function重新赋值。
- a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
- a_stand_alone_function()
- # 输出 :
- # Before the function runs
- # I am a stand alone function, don't you dare modify me
- # After the function runs
- # And guess what, that's EXACTLY what decorators do !
装饰器揭秘
前面的例子,我们可以使用装饰器的语法:
- @my_shiny_new_decorator
- def another_stand_alone_function():
- print("Leave me alone")
-
-
- another_stand_alone_function()
- # 输出 :
- # Before the function runs
- # Leave me alone
- # After the function runs
当然你也可以累积装饰:
- def bread(func):
- def wrapper():
- print(r"</''''''\>")
- func()
- print(r"<\______/>")
- return wrapper
-
-
- def ingredients(func):
- def wrapper():
- print("#tomatoes#")
- func()
- print("~salad~")
- return wrapper
-
-
- def sandwich(food="--ham--"):
- print(food)
-
-
- sandwich()
- # 输出 : --ham--
- sandwich = bread(ingredients(sandwich))
- sandwich()
- # outputs :
- # </''''''\>
- # #tomatoes#
- # --ham--
- # ~salad~
- # <\______/>
使用 python 装饰器语法:
- @bread
- @ingredients
- def sandwich(food="--ham--"):
- print(food)
-
-
- sandwich()
- # 输出 :
- # </''''''\>
- # #tomatoes#
- # --ham--
- # ~salad~
- # <\______/>
装饰器的顺序很重要,需要注意:
- @ingredients
- @bread
- def strange_sandwich(food="--ham--"):
- print(food)
-
-
- strange_sandwich()
- # 输出 :
- # tomatoes#
- # </''''''\>
- # --ham--
- # <\______/>
- # ~salad~
最后回答前面提到的问题:
- # 装饰器 make_bold 用于转换为粗体
- def make_bold(fn):
- # 结果返回该函数
- def wrapper():
- # 插入一些执行前后的代码
- return "<b>" + fn() + "</b>"
- return wrapper
-
-
- # 装饰器 make_italic 用于转换为斜体
- def make_italic(fn):
- # 结果返回该函数
- def wrapper():
- # 插入一些执行前后的代码
- return "<i>" + fn() + "</i>"
- return wrapper
-
-
- @make_bold
- @make_italic
- def say():
- return "hello"
-
-
- print(say())
- # 输出: <b><i>hello</i></b>
-
-
- # 等同于
-
- def say():
- return "hello"
-
-
- say = make_bold(make_italic(say))
- print(say())
- # 输出: <b><i>hello</i></b>
内置的装饰器
内置的装饰器有三个,分别是staticmethod、classmethod和property,作用分别是把类中定义的实例方法变成静态方法、类方法和类属性。由于模块里可以定义函数,所以静态方法和类方法的用处并不是太多,除非你想要完全的面向对象编程。而属性也不是不可或缺的,Java没有属性也一样活得很滋润。从我个人的Python经验来看,我没有使用过property,使用staticmethod和classmethod的频率也非常低。
- class Rabbit(object):
-
- def __init__(self, name):
- self._name = name
-
- @staticmethod
- def newRabbit(name):
- return Rabbit(name)
-
- @classmethod
- def newRabbit2(cls):
- return Rabbit('')
-
- @property
- def name(self):
- return self._name
这里定义的属性是一个只读属性,如果需要可写,则需要再定义一个setter:
@name.setter
def name(self, name):
self._name = name
functools模块
functools模块提供了两个装饰器。这个模块是Python 2.5后新增的,一般来说大家用的应该都高于这个版本。但我平时的工作环境是2.4 T-T
2.3.1. wraps(wrapped[, assigned][, updated]):
这是一个很有用的装饰器。看过前一篇反射的朋友应该知道,函数是有几个特殊属性比如函数名,在被装饰后,上例中的函数名foo会变成包装函数的名字wrapper,如果你希望使用反射,可能会导致意外的结果。这个装饰器可以解决这个问题,它能将装饰过的函数的特殊属性保留。
- import time
- import functools
-
- def timeit(func):
- @functools.wraps(func)
- def wrapper():
- start = time.clock()
- func()
- end =time.clock()
- print 'used:', end - start
- return wrapper
-
- @timeit
- def foo():
- print 'in foo()'
-
- foo()
- print foo.__name__
首先注意第5行,如果注释这一行,foo.__name__将是'wrapper'。另外相信你也注意到了,这个装饰器竟然带有一个参数。实际上,他还有另外两个可选的参数,assigned中的属性名将使用赋值的方式替换,而updated中的属性名将使用update的方式合并,你可以通过查看functools的源代码获得它们的默认值。对于这个装饰器,相当于wrapper = functools.wraps(func)(wrapper)。
深入浅出 Python 装饰器:16 步轻松搞定 Python 装饰器
而Python使用了一种相对于Decorator Pattern和Annotation来说非常优雅的方法,这种方法不需要你去掌握什么复杂的OO模型或是Annotation的各种类库规定,完全就是语言层面的玩法:一种函数式编程的技巧。如果你看过本站的《函数式编程》,你一定会为函数式编程的那种“描述你想干什么,而不是描述你要怎么去实现”的编程方式感到畅快。(如果你不了解函数式编程,那在读本文之前,还请你移步去看看《函数式编程》) 好了。
1. 函数
在python中,函数通过def关键字、函数名和可选的参数列表定义。通过return关键字返回值。我们举例来说明如何定义和调用一个简单的函数:
- def foo():
- return 1
- foo()
- 1
方法体(当然多行也是一样的)是必须的,通过缩进来表示,在方法名的后面加上双括号()就能够调用函数
2. 作用域
python中,函数会创建一个新的作用域。python开发者可能会说函数有自己的命名空间,差不多一个意思。这意味着在函数内部碰到一个变量的时候函数会优先在自己的命名空间里面去寻找。让我们写一个简单的函数看一下 本地作用域 和 全局作用域有什么不同:
- a_string = "This is a global variable"
- def foo():
- print locals()
- print globals()
- {..., 'a_string': 'This is a global variable'}
- foo() # 2
- {}
内置的函数globals返回一个包含所有python解释器知道的变量名称的字典(为了干净和洗的白白的,我省略了python自行创建的一些变量)。在#2我调用了函数 foo 把函数内部本地作用域里面的内容打印出来。我们能够看到,函数foo有自己独立的命名空间,虽然暂时命名空间里面什么都还没有。
3. 变量解析规则
当然这并不是说我们在函数里面就不能访问外面的全局变量。在python的作用域规则里面,创建变量一定会在当前作用域里创建一个变量,但是访问或者修改变量时会先在当前作用域查找变量,没有找到匹配变量的话会依次向上在闭合的作用域里面进行查看找。所以如果我们修改函数foo的实现让它打印全局的作用域里的变量也是可以的:
- a_string = "This is a global variable"
- def foo():
- print a_string # 1
- foo()
- This is a global variable
在#1处,python解释器会尝试查找变量a_string,当然在函数的本地作用域里面找不到,所以接着会去上层的作用域里面去查找。
但是另一方面,假如我们在函数内部给全局变量赋值,结果却和我们想的不一样:
- a_string = "This is a global variable"
- def foo():
- a_string = "test" # 1
- print locals()
- foo()
- {'a_string': 'test'}
- a_string # 2
- 'This is a global variable'
我们能够看到,全局变量能够被访问到(如果是可变数据类型(像list,dict这些)甚至能够被更改)但是赋值不行。在函数内部的#1处,我们实际上新创建了一个局部变量,隐藏全局作用域中的同名变量。我们可以通过打印出局部命名空间中的内容得出这个结论。我们也能看到在#2处打印出来的变量a_string的值并没有改变。
4. 变量生存周期
值得注意的一个点是,变量不仅是生存在一个个的命名空间内,他们都有自己的生存周期,请看下面这个例子:
- def foo():
- x = 1
- foo()
- print x # 1
- #Traceback (most recent call last):
- #NameError: name 'x' is not defined
#1处发生的错误不仅仅是因为作用域规则导致的(尽管这是抛出了NameError的错误的原因)它还和python以及其它很多编程语言中函数调用实现的机制有关。在这个地方这个执行时间点并没有什么有效的语法让我们能够获取变量x的值,因为它这个时候压根不存在!函数foo的命名空间随着函数调用开始而开始,结束而销毁。
5. 函数参数
python允许我们向函数传递参数,参数会变成本地变量存在于函数内部。
- def foo(x):
- print locals()
- foo(1)
- {'x': 1}
在Python里有很多的方式来定义和传递参数,完整版可以查看 python官方文档。我们这里简略的说明一下:函数的参数可以是必须的位置参数或者是可选的命名,默认参数。
- def foo(x, y=0): # 1
- return x - y
- foo(3, 1) # 2
- 2
- foo(3) # 3
- 3
- foo() # 4
- #Traceback (most recent call last):
- #TypeError: foo() takes at least 1 argument (0 given)
- foo(y=1, x=3) # 5
- 2
在#1处我们定义了函数foo,它有一个位置参数x和一个命名参数y。在#2处我们能够通过常规的方式来调用函数,尽管有一个命名参数,但参数依然可以通过位置传递给函数。在调用函数的时候,对于命名参数y我们也可以完全不管就像#3处所示的一样。如果命名参数没有接收到任何值的话,python会自动使用声明的默认值也就是0。需要注意的是我们不能省略第一个位置参数x, 否则的话就会像#4处所示发生错误。
目前还算简洁清晰吧, 但是接下来可能会有点令人困惑。python支持函数调用时的命名参数(个人觉得应该是命名实参)。看看#5处的函数调用,我们传递的是两个命名实参,这个时候因为有名称标识,参数传递的顺序也就不用在意了。
当然相反的情况也是正确的:函数的第二个形参是y,但是我们通过位置的方式传递值给它。在#2处的函数调用foo(3,1),我们把3传递给了第一个参数,把1传递给了第二个参数,尽管第二个参数是一个命名参数。
一个简单的概念:函数的参数可以有名称和位置。这意味着在函数的定义和调用的时候会稍稍在理解上有点儿不同。我们可以给只定义了位置参数的函数传递命名参数(实参),反之亦然!如果觉得不够可以查看官方文档
6. 嵌套函数
Python允许创建嵌套函数。这意味着我们可以在函数里面定义函数而且现有的作用域和变量生存周期依旧适用。
- def outer():
- x = 1
- def inner():
- print x # 1
- return inner() # 2
- outer()
- 1
这个例子有一点儿复杂,但是看起来也还行。想一想在#1发生了什么:python解释器需找一个叫x的本地变量,查找失败之后会继续在上层的作用域里面寻找,这个上层的作用域定义在另外一个函数里面。对函数outer来说,变量x是一个本地变量,但是如先前提到的一样,函数inner可以访问封闭的作用域(至少可以读和修改)。在#2处,我们调用函数inner,非常重要的一点是,inner也仅仅是一个遵循python变量解析规则的变量名,python解释器会优先在outer的作用域里面对变量名inner查找匹配的变量.
7. 函数是 python 世界里的一级类对象
显而易见,在python里函数和其他东西一样都是对象。(此处应该大声歌唱)啊!包含变量的函数,你也并不是那么特殊!
- issubclass(int, object) # all objects in Python inherit from a common baseclass
- #True
- def foo():
- pass
- foo.__class__ # 1
- #<type 'function'>
- issubclass(foo.__class__, object)
- #True
你也许从没有想过,你定义的函数居然会有属性。没办法,函数在python里面就是对象(Python一切皆对象),和其他的东西一样,也许这样描述会太学院派太官方了点:在python里,函数只是一些普通的值而已和其他的值一毛一样。这就是说你可以把函数像参数一样传递给其他的函数或者说从函数里面返回函数!如果你从来没有这么想过,那看看下面这个例子:
- def add(x, y):
- return x + y
- def sub(x, y):
- return x - y
- def apply(func, x, y): # 1
- return func(x, y) # 2
- apply(add, 2, 1) # 3
- 3
- apply(sub, 2, 1)
- 1
这个例子对你来说应该不会很奇怪。add和sub是非常普通的两个python函数,接受两个值,返回一个计算后的结果值。在#1处你们能看到准备接收一个函数的变量只是一个普通的变量而已,和其他变量一样。在#2处我们调用传进来的函数:“()代表着调用的操作并且调用变量包含的值。在#3处,你们也能看到传递函数并没有什么特殊的语法。” 函数的名称只是很其他变量一样的表标识符而已。
你们也许看到过这样的行为:“python把频繁要用的操作变成函数作为参数进行使用,像通过传递一个函数给内置排序函数的key参数从而来自定义排序规则。那把函数当做返回值回事这样的情况呢:
- def outer():
- def inner():
- print "Inside inner"
- return inner # 1
- foo = outer() #2
- foo
- #<function inner at 0x...>
- foo()
- #Inside inner
这个例子看起来也许会更加的奇怪。在#1处我把恰好是函数标识符的变量inner作为返回值返回出来。这并没有什么特殊的语法:”把函数inner返回出来,否则它根本不可能会被调用到。“还记得变量的生存周期吗?每次函数outer被调用的时候,函数inner都会被重新定义,如果它不被当做变量返回的话,每次执行过后它将不复存在。
在#2处我们捕获住返回值 – 函数inner,将它存在一个新变量foo里。我们能够看到,当对变量foo进行求值,它确实包含函数inner,而且我们能够对他进行调用。初次看起来可能会觉得有点奇怪,但是理解起来并不困难是吧。坚持住,因为奇怪的转折马上就要来了
8. 闭包
我们先不急着定义什么是闭包,先来看看一段代码,仅仅是把上一个例子简单的调整了一下:
- def outer():
- x = 1
- def inner():
- print x # 1
- return inner
- foo = outer()
- foo.func_closure
- #(<cell at 0x...: int object at 0x...>,)
在上一个例子中我们了解到,inner作为一个函数被outer返回,保存在一个变量foo,并且我们能够对它进行调用foo()。不过它会正常的运行吗?我们先来看看作用域规则。
所有的东西都在python的作用域规则下进行工作:“x是函数outer里的一个局部变量。当函数inner在#1处打印x的时候,python解释器会在inner内部查找相应的变量,当然会找不到,所以接着会到封闭作用域里面查找,并且会找到匹配。
但是从变量的生存周期来看,该怎么理解呢?我们的变量x是函数outer的一个本地变量,这意味着只有当函数outer正在运行的时候才会存在。根据我们已知的python运行模式,我们没法在函数outer返回之后继续调用函数inner,在函数inner被调用的时候,变量x早已不复存在,可能会发生一个运行时错误。
万万没想到,返回的函数inner居然能够正常工作。Python支持一个叫做函数闭包的特性,用人话来讲就是,嵌套定义在非全局作用域里面的函数能够记住它在被定义的时候它所处的封闭命名空间。这能够通过查看函数的func_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,我们实际上是创建了能够打印各种数字的各种自定义版本。
闭包单独拿出来就是一个非常强大的功能, 在某些方面,你也许会把它当做一个类似于面向对象的技术:outer像是给inner服务的构造器,x像一个私有变量。使用闭包的方式也有很多:你如果熟悉python内置排序方法的参数key,你说不定已经写过一个lambda方法在排序一个列表的列表的时候基于第二个元素而不是第一个。现在你说不定也可以写一个itemgetter方法,接收一个索引值来返回一个完美的函数,传递给排序函数的参数key。
不过,我们现在不会用闭包做这么low的事(⊙o⊙)…!相反,让我们再爽一次,写一个高大上的装饰器!
9. 装饰器
装饰器其实就是一个闭包,把一个函数当做参数然后返回一个替代版函数。我们一步步从简到繁来瞅瞅:
- def outer(some_func):
- def inner():
- print "before some_func"
- ret = some_func() # 1
- return ret + 1
- return inner
- def foo():
- return 1
- decorated = outer(foo) # 2
- decorated()
- #before some_func
- #2
仔细看看上面这个装饰器的例子。们定义了一个函数outer,它只有一个some_func的参数,在他里面我们定义了一个嵌套的函数inner。inner会打印一串字符串,然后调用some_func,在#1处得到它的返回值。在outer每次调用的时候some_func的值可能会不一样,但是不管some_func的之如何,我们都会调用它。最后,inner返回some_func() + 1的值 – 我们通过调用在#2处存储在变量decorated里面的函数能够看到被打印出来的字符串以及返回值2,而不是期望中调用函数foo得到的返回值1。
我们可以认为变量decorated是函数foo的一个装饰版本,一个加强版本。事实上如果打算写一个有用的装饰器的话,我们可能会想愿意用装饰版本完全取代原先的函数foo,这样我们总是会得到我们的”加强版“foo。想要达到这个效果,完全不需要学习新的语法,简单地赋值给变量foo就行了:
- foo = outer(foo)
- foo # doctest: +ELLIPSIS
- #<function inner at 0x...>
现在,任何怎么调用都不会牵扯到原先的函数foo,都会得到新的装饰版本的foo。
假设有如下函数:
- def now():
- print '2013-12-25'
- f = now
- f()
- #2013-12-25
现在假设我们要增强now()
函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()
函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:
- def log(func):
- def wrapper(*args, **kw):
- print 'call %s():' % func.__name__
- return func(*args, **kw)
- return wrapper
观察上面的log
,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。
10. 使用 @ 标识符将装饰器应用到函数
Python2.4支持使用标识符@将装饰器应用在函数上,只需要在函数的定义前加上@和装饰器的名称。在上一节的例子里我们是将原本的方法用装饰后的方法代替:
add = wrapper(add)
这种方式能够在任何时候对任意方法进行包装。但是如果我们自定义一个方法,我们可以使用@进行装饰:
- @wrapper
- def add(a, b):
- return Coordinate(a.x + b.x, a.y + b.y)
需要明白的是,这样的做法和先前简单的用包装方法替代原有方法是一模一样的, python只是加了一些语法糖让装饰的行为更加的直接明确和优雅一点。
多个decorator
- @decorator_one
- @decorator_two
- def func():
- pass
相当于:
func = decorator_one(decorator_two(func))
比如:带参数的decorator:
- @decorator(arg1, arg2)
- def func():
- pass
相当于:
func = decorator(arg1,arg2)(func)
这意味着decorator(arg1, arg2)这个函数需要返回一个“真正的decorator”。
11. *args and **kwargs
我们已经完成了一个有用的装饰器,但是由于硬编码的原因它只能应用在一类具体的方法上,这类方法接收两个参数,传递给闭包捕获的函数。如果我们想实现一个能够应用在任何方法上的装饰器要怎么做呢?再比如,如果我们要实现一个能应用在任何方法上的类似于计数器的装饰器,不需要改变原有方法的任何逻辑。这意味着装饰器能够接受拥有任何签名的函数作为自己的被装饰方法,同时能够用传递给它的参数对被装饰的方法进行调用。
非常巧合的是Python正好有支持这个特性的语法。可以阅读 Python Tutorial 获取更多的细节。当定义函数的时候使用了*,意味着那些通过位置传递的参数将会被放在带有*前缀的变量中, 所以:
- def one(*args):
- print args # 1
- one()
- #()
- one(1, 2, 3)
- #(1, 2, 3)
- def two(x, y, *args): # 2
- print x, y, args
- two('a', 'b', 'c')
- #a b ('c',)
第一个函数one只是简单地讲任何传递过来的位置参数全部打印出来而已,你们能够看到,在代码#1处我们只是引用了函数内的变量args, *args仅仅只是用在函数定义的时候用来表示位置参数应该存储在变量args里面。Python允许我们制定一些参数并且通过args捕获其他所有剩余的未被捕捉的位置参数,就像#2处所示的那样。
*操作符在函数被调用的时候也能使用。意义基本是一样的。当调用一个函数的时候,一个用*标志的变量意思是变量里面的内容需要被提取出来然后当做位置参数被使用。同样的,来看个例子:
- def add(x, y):
- return x + y
- lst = [1,2]
- add(lst[0], lst[1]) # 1
- 3
- add(*lst) # 2
- 3
#1处的代码和#2处的代码所做的事情其实是一样的,在#2处,python为我们所做的事其实也可以手动完成。这也不是什么坏事,*args要么是表示调用方法大的时候额外的参数可以从一个可迭代列表中取得,要么就是定义方法的时候标志这个方法能够接受任意的位置参数。
接下来提到的**会稍多更复杂一点,**代表着键值对的餐宿字典,和*所代表的意义相差无几,也很简单对不对:
- def foo(**kwargs):
- print kwargs
- foo()
- #{}
- foo(x=1, y=2)
- #{'y': 2, 'x': 1}
当我们定义一个函数的时候,我们能够用**kwargs来表明,所有未被捕获的关键字参数都应该存储在kwargs的字典中。如前所诉,args、kwargs并不是python语法的一部分,但在定义函数的时候,使用这样的变量名算是一个不成文的约定。和*一样,我们同样可以在定义或者调用函数的时候使用**。
- dct = {'x': 1, 'y': 2}
- def bar(x, y):
- return x + y
- bar(**dct)
- #3
12. 更通用的装饰器
有了这招新的技能,我们随随便便就可以写一个能够记录下传递给函数参数的装饰器了。先来个简单地把日志输出到界面的例子:
- def logger(func):
- def inner(*args, **kwargs): #1
- print "Arguments were: %s, %s" % (args, kwargs)
- return func(*args, **kwargs) #2
- return inner
请注意函数inner,它能够接受任意数量和类型的参数并把它们传递给被包装的方法,这让我们能够用这个装饰器来装饰任何方法。
- @logger
- def foo1(x, y=1):
- return x * y
- @logger
- def foo2():
- return 2
- foo1(5, 4)
- #Arguments were: (5, 4), {}
- #20
- foo1(1)
- #Arguments were: (1,), {}
- #1
- foo2()
- #Arguments were: (), {}
- #2
随便调用我们定义的哪个方法,相应的日志也会打印到输出窗口,和我们预期的一样。
13. 带参数的装饰器:
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
- def log(text):
- def decorator(func):
- def wrapper(*args, **kw):
- print '%s %s():' % (text, func.__name__)
- return func(*args, **kw)
- return wrapper
- return decorator
这个3层嵌套的decorator用法如下:
- @log('execute')
- def now():
- print '2013-12-25'
执行结果如下:
- >>> now()
- execute now():
- 2013-12-25
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
now = log('execute')(now)
我们来剖析上面的语句,首先执行log('execute')
,返回的是decorator
函数,再调用返回的函数,参数是now
函数,返回值最终是wrapper
函数。
14. 装饰器的副作用
以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__
等属性,但你去看经过decorator装饰之后的函数,它们的__name__
已经从原来的'now'
变成了'wrapper'
:
- >>> now.__name__
- 'wrapper'
因为返回的那个wrapper()
函数名字就是'wrapper'
,所以,需要把原始函数的__name__
等属性复制到wrapper()
函数中,否则,有些依赖函数签名的代码执行就会出错。
不需要编写wrapper.__name__ = func.__name__
这样的代码,Python内置的functools.wraps
就是干这个事的,所以,一个完整的decorator的写法如下:
- import functools
-
- def log(func):
- @functools.wraps(func)
- def wrapper(*args, **kw):
- print 'call %s():' % func.__name__
- return func(*args, **kw)
- return wrapper
或者针对带参数的decorator:
- import 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
import functools
是导入functools
模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()
的前面加上@functools.wraps(func)
即可。
当然,即使是你用了functools的wraps,也不能完全消除这样的副作用。你会发现,即使是你你用了functools的wraps,你在用getargspec时,参数也不见了。要修正这一问题,我们还得用Python的反射来解决,当然,我相信大多数人的程序都不会去getargspec。所以,用functools的wraps应该够用了。
15. class式的 Decorator
首先,先得说一下,decorator的class方式,还是看个示例:
- class myDecorator(object):
-
- def __init__(self, fn):
- print "inside myDecorator.__init__()"
- self.fn = fn
-
- def __call__(self):
- self.fn()
- print "inside myDecorator.__call__()"
-
- @myDecorator
- def aFunction():
- print "inside aFunction()"
-
- print "Finished decorating aFunction()"
-
- aFunction()
-
- # 输出:
- # inside myDecorator.__init__()
- # Finished decorating aFunction()
- # inside aFunction()
- # inside myDecorator.__call__()
1)一个是__init__(),这个方法是在我们给某个函数decorator时被调用,所以,需要有一个fn的参数,也就是被decorator的函数。
2)一个是__call__(),这个方法是在我们调用被decorator函数时被调用的。
上面输出可以看到整个程序的执行顺序。
这看上去要比“函数式”的方式更易读一些。
上面这段代码中,我们需要注意这几点:
1)如果decorator有参数的话,__init__() 成员就不能传入fn了,而fn是在__call__的时候传入的。
16. 一些decorator的示例
好了,现在我们来看一下各种decorator的例子:
16.1 给函数调用做缓存
这个例实在是太经典了,整个网上都用这个例子做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,在调用函数前查询一下缓存,如果没有才调用了,有了就从缓存中返回值。一下子,这个递归从二叉树式的递归成了线性的递归。
16.2 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
16.3 注册回调函数
下面这个示例展示了通过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返回了原函数。所以,原函数没有发生任何变化。
16.4 给函数打日志
下面这个示例演示了一个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原则。
16.5 一个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):
- ... ...
- ... ..
16.6 线程异步
下面量个非常简单的异步执行的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()
16.7 超时函数
这个函数的作用在于可以给任意可能会hang住的函数添加超时功能,这个功能在编写外部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) # 被终止
16.8 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
16.9 单例模式
示例代码:
- # 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
16.10 LRUCache
下面要分享的这个LRUCache不是我做的,是github上的一个库,我们在实际环境中有用到。
先来说下这个概念,cache的意思就是缓存,LRU就是Least Recently Used,即最近最少使用,是一种内存管理算法。总结来说这就是一种缓存方法,基于时间和容量。
一般在简单的python程序中,遇到需要处理缓存的情况时最简单的方式,声明一个全局的dict就能解决(在python中应尽量避免使用全局变量)。但是只是简单情况,这种情况会带来的问题就是内存泄漏,因为可能会出现一直不命中的情况。
由此导致的一个需求就是,你要设定这个dict的最大容量,防止发生泄漏。但仅仅是设定最大容量是不够的,设想当你的dict变量已被占满,还是没有命中,该如何处理。
这时就需要加一个失效时间了。如果在指定失效期内没有使用到该缓存,则删除。
综述上面的需求和功能就是LRUCache干的事了。不过这份代码做了更进一层的封装,可以让你直接把缓存功能做为一个装饰器来用。具体实现可以去参考代码,别人实现之后看起来并不复杂 :)
- from lru import lru_cache_function
-
- @lru_cache_function(max_size=1024, expiration=15*60)
- def f(x):
- print "Calling f(" + str(x) + ")"
- return x
-
- f(3) # This will print "Calling f(3)", will return 3
- f(3) # This will not print anything, but will return 3 (unless 15 minutes have passed between the first and second function call).
代码: https://github.com/the5fire/Python-LRU-cache/blob/master/lru.py
从python3.2开始内置在functools了lru_cache的功能,说明这个需求是很普遍的。
17. 小结
在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。
decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。
最后留个小作业:
请编写一个decorator,能在函数调用的前后打印出'begin call'
和'end call'
的日志。
再思考一下能否写出一个@log
的decorator,使它既支持:
- @log
- def f():
- pass
又支持:
- @log('execute')
- def f():
- pass
18. Refer:
[1] 12步轻松搞定python装饰器
http://python.jobbole.com/81683/
[2] 装饰器
[3] Python修饰器的函数式编程
http://coolshell.cn/articles/11265.html
[4] Python Decorator Library
https://wiki.python.org/moin/PythonDecoratorLibrary
[5] Python装饰器实例:调用参数合法性验证
http://python.jobbole.com/82114/
[6] Python 装饰器
http://python.jobbole.com/82344/
[7] 两个实用的Python的装饰器
[8] Python 中的闭包总结
[9] Python 的闭包和装饰器
https://segmentfault.com/a/1190000004461404
[10] Python修饰器的问题