目录摘要
零、前言
一、一段tornado的代码——接触装饰器
二、python装饰器要素
零、前言
本文一开始是在codeview时临场写下的笔记,所以开篇有tornado的源码的摘要,例证中也贴了一些robotframework的源码 。然后逐步补充成文的。可能作为入门文章有些欠直接。
在补缀成文的过程中,我围绕装饰器的是什么,装饰器有什么用途以及装饰器应当怎么写三个问题力图把python这个语法特性让自己深入掌握,也希望能帮助有缘人。
错漏之处在所难免,本文也在不断补充修正之中。
笔者每有新体会会来回看文章看看以前有没有理解错误的地方。
关于文章的结构,我没有惯例式地使用一二三来“条分缕析”,因为我写的时候一直围绕上述的三大问题,一二三太丑。
一、一段tornado的代码——接触装饰器
装饰器的数学模型是复合函数 :
(f * g) (x) = f(g(x))
下面是摘自tornado中的一段代码:
class ComposeHandler(BaseHandler):
@administrator
def get(self):
key = self.get_argument("key", None)
entry = Entry.get(key) if key else None
self.render("compose.html", entry=entry)
administrator 是一个装饰器,在给服务器发出get请求,路由到这个ComposeHandler的时候,需要先运行administrator的逻辑。这个类似Java中的拦截器语法(AOP)。
administrator也是一个函数,它的样子是这样的:
def administrator(method):
"""Decorate with this method to restrict to site admins."""
@functools.wraps(method)
def wrapper(self, *args, **kwargs):
if not self.current_user:
if self.request.method == "GET":
self.redirect(self.get_login_url())
return
raise tornado.web.HTTPError(403)
elif not self.current_user.administrator:
if self.request.method == "GET":
self.redirect("/")
return
raise tornado.web.HTTPError(403)
else:
return method(self, *args, **kwargs)
return wrapper
这里使用了一个闭包函数wrapper(可调用的对象),它在函数内部被定义,当返回给上层调用者时,这个wrapper会在get函数调用之前运行起来,从而起到装饰的作用。回想一下,装饰模式的表述:用对客户透明的方式给一个对象动态地增加功能。python的装饰器展示的也是这种思想,但是python的装饰器强调的更多还是AOP得概念,用简洁的@语法抽象出公共功能,避免了一段“公共的客户代码”散落在代码各处的毛病,避免了维护的困难。
由于装饰器可以重叠装饰,所以它也符合了“动态增加功能的特点”,所以我把它理解成装饰模式的一种应用。
二、python装饰器要素
总结一下python装饰器的几大要点:
1、装饰器是一种特殊的函数
2、装饰器的参数通常有一个是函数对象
3、装饰器要返回一个函数对象
- 第一点是因为
@deco
def f():
...
的调用等价于 g = deco(f) ,g(); deco必须是一个可调用的对象。
- 第二点是因为装饰器的功能一般是对被修饰的对象的一种操作,所以它会接收一个函数对象进行操作,但这一点不是必须的,看这段代码(来自robotframework的对函数进行keword修饰的代码) 参数可以是其他数据类型:
@not_keyword
def keyword(name=None, tags=(), types=()):
"""
[此处省略了源代码的文档说明]
"""
if inspect.isroutine(name):
return keyword()(name)
def decorator(func):
func.robot_name = name
func.robot_tags = tags
func.robot_types = types
return func
return decorator
第一个参数name可以是一个字符串,这种写法的keyword的用法可以有如下一些方式:
@keyword
def example():
# ...
@keyword('Login as user "${user}" with password "${password}"',
tags=['custom name', 'embedded arguments', 'tags'])
def login(user, password):
# ...
@keyword(types={'length': int, 'case_insensitive': bool})
def types_as_dict(length, case_insensitive):
# ...
@keyword(types=[int, bool])
def types_as_list(length, case_insensitive):
# ...
@keyword(types=None])
def no_conversion(length, case_insensitive=False):
# ...
- 第三点是最重要的一点,装饰器一定要最终返回一个函数对象。因为不这么做的话,装饰器默认返回一个None,使用语法糖 @decorator的时候,解释器会抛出一个TypeError
TypeError: 'NoneType' object is not callable
第三条准则还需要注意的点是,最终返回,形如如下的代码:
def mydecorator(funcname, tags=[]):
def wrapper(func):
print("exe something")
return wrapper
可以用作函数的装饰器吗?答案是否定的,**因为函数的最终返回还是None **,除非改成下面的样子:
def mydecorator(funcname, tags=[]):
def wrapper(func):
print("exe something")
return func #显示得返回函数对象,这个函数对象在使用语法糖 @mydecorator def f():.... 时将会是被修饰函数f自己
return wrapper
那么什么是装饰器?python的文档给了一个言简意赅的定义: https://docs.python.org/zh-cn/3/glossary.html#term-decorator
即
返回值为另一个函数的函数,通常使用 @wrapper 语法形式来进行函数变换
装饰器语法只是一种语法糖,以下两个函数定义在语义上完全等价【文档中举的一个例子】:
def f(...):
...
f = staticmethod(f)
@staticmethod
def f(...):
...
函数式的装饰器例子:
假设我们要给一个模块里的函数都加上追踪调试信息,函数进入时输出一段时间戳。使用装饰器,先定义一个打印调试追踪信息的装饰器。
def log_deco(method):
def wrapper():
print time.strftime("%Y-%m-%d %H:%M:%S"),
print "Call funcion : %s" % method.__name__
return method() # method在这里调用 也可以不写return
return wrapper
@log_deco
def func1():
print "Processing something..."
print "Calculating something ..."
@log_deco
def func2():
print "Processing something2..."
print "Calculating something2..."
if __name__ == '__main__':
func1()
func2()
样例输出:
2017-09-19 11:20:41 Call funcion : func1
Processing something...
Calculating something ...
2017-09-19 11:20:41 Call funcion : func2
Processing something2...
Calculating something2...
调用func1()的时候,会首先执行w = log_doco(func1),log_doco在内部定义了wrapper, 如果考察一下 w会发现,它就是log_deco内部定义的内部函数wrapper,只需要验证一下它的name和对象id就行;
@log_deco def ... 等价于
w = log_demo(func1)
w()
w()实际上就是调用wrapper()
内建装饰器 classmethod和staticmethod
classmethod和staticmethod是python的内建方法。它们是都是类。
在构造器中接受一个函数对象作参数,并返回一个函数对象的实例。
怎么在python里面写静态方法?函数声明的前面用 @staticmethod装饰一下就行。
class Foo:
@staticmethod
def func1():
print("demo func1..")
调用时可以直接用类名访问,或者实例访问也行。静态方法是一个与实例无关的方法,和Java、C++的静态方法是一样的概念。
python中的类方法和静态方法有什么区别?
1、静态方法的概念和Java、C++一样,它不和类、类的实例绑定,通过类名调用,也可通过实例调用,结果都是一样的;
2、类方法有一个cls的实参传入,代表与方法绑定的类对象;类方法与类绑定,而一般的方法是与实例绑定。类方法可以通过类名调用,也可以通过类的实例调用。
三、装饰器的几种实现写法
写一个装饰器的目的本身是为了吃“语法糖”,让我们能在程序各处潇洒地用@wrapper的语法,那么围绕这个@wrapper的语法,我们总结一下有哪些可以实现装饰器的实践方式。
上面提到了,装饰器的定义是一个返回值为另一个函数的函数,我们在实现装饰器的时候,紧紧围绕这个点,然后作几个变化就会有多种写法
做一个脑图,大概列举一下装饰器的实现形式
Invalid的情形以举了例子,下面着重举出valid的情形中,类形式的装饰器,以及最常见的返回内部定义函数的情况(实际上,上面的keword装饰器就是属于这种)
1. 使用可调用对象做装饰器
2. 返回值在函数内部定义
3. 类装饰器
类的 __call__方法被定义时,这个类的实例也是可被调用的。
例子,将一个类的定义实现 _call_ 协议,那么所有该类的实例都是一个可调用对象。
class CallDemo:
def __init__(self):
print "This is a class for callable object"
def __call__(self, *args):
print("Call ... run here ...")
return 100
c = CallDemo()
print(c()) # 实例调用
如果没有 重写 __call__方法 ,实例调用 c( ) 会导致解释器会报告一个AttributeError: CallDemo instance has no __call__ method 的错误
借助 __call__ ,写一个类装饰器, 为我的函数盖一个戳子:
class sign:
def __init__(self, func):
self.func = func # 被修饰的函数对象从这里传进来
def __call__(self, *args):
print "== Dong hua's func =="
self.func() # 函数调用被移到了这里
print "== leave func == "
@sign
def foo():
print ("... This is foo function ...")
foo()
这里的类装饰器,需要在构造器上接收一个函数对象做参数,并重新定义 __call__函数。当调用foo()函数的时候,相当于
f = sign(foo)
f() #用类的实例调用,所以触发了 __call__的逻辑