1 2 from IPython.core.interactiveshell import InteractiveShellInteractiveShell.ast_node_interactivity = 'all'
装饰器(Decorators) 装饰器(Decorators)是 Python 的一个重要部分。简单地说:他们是修改其他函数的功能的函数。他们有助于让我们的代码更简短,也更Pythonic(Python范儿)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def hi (name=" world" ): return "hello" + name hi() greet = hi greet() del hi greet()
1 2 3 4 5 'hello world' 'hello world' 'hello world'
在函数中定义函数 刚才那些就是函数的基本知识了。我们来让你的知识更进一步。在 Python 中我们可以在一个函数中定义另一个函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def testfunction (parameter="info" ): print ("现在会调用里层函数" ) def greet (): return "greet函数被调用" def welcome (): return "welcome函数被调用" def gentleman (): print ("gentleman函数被调用" ) print (greet()) print (welcome()) gentleman() print ("函数调用结束" ) testfunction()
1 2 3 4 5 现在会调用里层函数 greet函数被调用 welcome函数被调用 gentleman函数被调用 函数调用结束
可以在函数中定义另外的函数。也就是说可以创建嵌套的函数。现在需要再多学一点,就是函数也能返回函数。
从函数中返回函数 其实并不需要在一个函数里去执行另一个函数,我们也可以将其作为输出返回出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def testfunction (parameter="info" ): def greet (): return "greet函数被调用" def welcome (): return "welcome函数被调用" if parameter == "info" : return greet else : return welcome a = testfunction() print (a)print (a())testfunction()()
1 2 3 4 <function testfunction.<locals>.greet at 0x7f9e00638f28> greet函数被调用 'greet函数被调用'
再次看看这个代码。在if/else语句中返回greet和welcome,而不是greet()和welcome()。 当把一对小括号放在后面,这个函数就会执行;然而如果不放括号在它后面,那它可以被到处传递,并且可以赋值给别的变量而不去执行它。 再稍微多解释点细节: 当我们写下a = testfunction(),testfunction()会被执行,而由于name参数默认是info,所以函数greet被返回了。如果我们把语句改为a = testfunction(name = “Alice”),那么welcome函数将被返回。 我们还可以打印出testfunction()(),这会输出greet函数被调用。
将函数作为参数传给另一个函数 1 2 3 4 5 6 def testfunction (): return "hello world!" def Do_Something_Before_testfunction (func ): print ("I am doing some boring work before executing hi()" ) print (func())
1 2 3 Do_Something_Before_testfunction(testfunction) I am doing some boring work before executing hi() hello world!
现在已经具备所有必需知识来学习装饰器是什么了,装饰器让你在一个函数的前后去执行代码。
装饰器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def function1 (a_func ): def wrapTheFunction (): print ("在执行a_func()之前我正在做一些无聊的工作" ) a_func() print ("在执行a_func()之后我正在做一些无聊的工作" ) return wrapTheFunction def function2 (): print ("函数2的输出" ) function2() function2 = function1(function2) function2()
1 2 3 4 5 6 函数2的输出 在执行a_func()之前我正在做一些无聊的工作 函数2的输出 在执行a_func()之后我正在做一些无聊的工作 刚刚应用了之前学习到的原理。这正是python中装饰器做的事情!它封装一个函数,并且用这样或者那样的方式来修改它的行为。 现在也许疑惑,在代码里并没有使用@符号?只是一个简短的方式来生成一个被装饰的函数。这里是我们如何使用@来运行之前的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 @function1 def a_function_requiring_decoration (): """Hey you! Decorate me!""" print ("I am the function which needs some decoration to " "remove my foul smell" ) a_function_requiring_decoration() a_function_requiring_decoration = function1(a_function_requiring_decoration)
1 2 3 在执行a_func()之前我正在做一些无聊的工作 I am the function which needs some decoration to remove my foul smell 在执行a_func()之后我正在做一些无聊的工作
希望现在对Python装饰器的工作原理有一个基本的理解。如果运行如下代码会存在一个问题:
1 2 print (a_function_requiring_decoration.__name__)
这并不是想要的!Ouput输出应该是a_function_requiring_decoration。这里的函数被warpTheFunction替代了。它重写了函数的名字和注释文档(docstring)。幸运的是Python提供给一个简单的函数来解决这个问题,那就是functools.wraps。修改上一个例子来使用functools.wraps:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from functools import wrapsdef a_new_decorator (a_func ): @wraps(a_func ) def wrapTheFunction (): print ("I am doing some boring work before executing a_func()" ) a_func() print ("I am doing some boring work after executing a_func()" ) return wrapTheFunction @a_new_decorator def a_function_requiring_decoration (): """Hey yo! Decorate me!""" print ("I am the function which needs some decoration to " "remove my foul smell" ) print (a_function_requiring_decoration.__name__)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 a_function_requiring_decoration from functools import wrapsdef decorator_name (f ): @wraps(f ) def decorated (*args, **kwargs ): if not can_run: return "Function will not run" return f(*args, **kwargs) return decorated @decorator_name def func (): return ("Function is running" ) can_run = True print (func())can_run = False print (func())
1 2 Function is running Function will not run
注意: @wraps接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性。 装饰器在某些地方特别耀眼,使用它可以让一些事情管理起来变得更简单。 装饰器可以用于日志管理 装饰器可以监控函数运行之前的状态,捕捉函数的运行情况,传入参数等等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from functools import wrapsdef logit (func ): @wraps(func ) def with_logging (*args, **kwargs ): print (func.__name__ + " was called" ) return func(*args, **kwargs) return with_logging @logit def addition_func (x ): """Do some math.""" return x + x result = addition_func(4 )
1 addition_func was called
带参数的装饰器 来想想这个问题,难道@wraps不也是个装饰器吗? 但是,它接收一个参数,就像任何普通的函数能做的那样。那么,为什么我们不也那样做呢? 这是因为,当使用@my_decorator语法时,是在应用一个以单个函数作为参数的一个包裹函数。记住,Python里每个东西都是一个对象,而且这包括函数! 记住了这些,我们可以编写一个能返回一个包裹函数的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 from functools import wrapsdef logit (logfile='out.log' ): def logging_decorator (func ): @wraps(func ) def wrapped_function (*args, **kwargs ): log_string = func.__name__ + " was called" print (log_string) with open (logfile, 'a' ) as opened_file: opened_file.write(log_string + '\n' ) return func(*args, **kwargs) return wrapped_function return logging_decorator @logit() def myfunc1 (): pass myfunc1() @logit(logfile='func2.log' ) def myfunc2 (): pass myfunc2()
1 2 myfunc1 was called myfunc2 was called
装饰器类 现在有了能用于正式环境的logit装饰器,但当应用的某些部分还比较脆弱时,异常也许是需要更紧急关注的事情。比方说有时想打日志到一个文件。而有时想把引起你注意的问题发送到一个email,同时也保留日志,留个记录。这是一个使用继承的场景,但目前为止只看到过用来构建装饰器的函数。 幸运的是,类也可以用来构建装饰器。那现在以一个类而不是一个函数的方式,来重新构建logit。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from functools import wrapsclass logit (object ): def __init__ (self, logfile='out.log' ): self.logfile = logfile def __call__ (self, func ): @wraps(func ) def wrapped_function (*args, **kwargs ): log_string = func.__name__ + " was called" print (log_string) with open (self.logfile, 'a' ) as opened_file: opened_file.write(log_string + '\n' ) self.notify() return func(*args, **kwargs) return wrapped_function def notify (self ): print ("打印日志" ) pass
这个实现有一个附加优势,在于比嵌套函数的方式更加整洁,而且包裹一个函数还是使用跟以前一样的语法:
1 2 3 4 5 @logit() def myfunc1 (): print ("开始" ) myfunc1()
1 2 3 myfunc1 was called 打印日志 开始
现在,我们给logit创建子类,来添加新的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class email_logit (logit ): ''' 一个logit的实现版本,可以在函数调用时发送email给管理员 ''' def __init__ (self, email='admin@myproject.com' , *args, **kwargs ): self.email = email super (email_logit, self).__init__(*args, **kwargs) def notify (self ): print ("假设已经发送了邮件" ) @email_logit() def myfunc1 (): print ("开始" ) myfunc1()
1 2 3 myfunc1 was called 假设已经发送了邮件 开始