Decorator vs Decorator Pattern
First of all, everyone needs to understand that there may be a lot of worries about using the word decorator, because it can easily be confused with the decorator pattern in the book Design Patterns. Some other terminology was considered for this new feature, but decorators won out.
It is true that you can use python decorators to implement the decorator pattern, but this is definitely a very small part of its functionality and is a bit of a waste. For python decorators, I think it is the closest thing to macros.
History of macro
Macros have a very long history, but most people may have experience using C language preprocessing macros. However, there are some problems with macros in C language. (1) Macros do not exist in C language, (2) and the behavior of macros can sometimes be a bit weird, and often differ from the behavior of C language. Too consistent.
In order to support operations on some elements of the language itself, both Java and C# have added annotations. Of course, they all have some problems: sometimes in order to achieve your goals, you have to bypass a lot of pitfalls. Not finished yet, these annotation features will also be constrained by some inherent features of these languages (just like "Directing" described by Martin Fowler)
The slightly different thing is that many C++ programmers, including me, have realized the power of C++ templates and are already using this feature like macros.
Many other languages also include macro functions. Although I don’t know much about them, I am still willing to say without shame that Python decorators are very similar to Lisp macros in terms of power and richness of functions.
Macro target
I think it’s not too much to describe macros like this: Macros exist in a programming language to provide the ability to manipulate the language elements themselves. This is exactly what python decorators can do. They can modify functions and decorate this class. Compared to complex metaclasses, this may be the reason why people often provide a simple decorator.
The self-modification (metaprogramming) solutions provided by most programming languages have a major shortcoming, that is, there are too many restrictions and constraints, and it gives the illusion of writing other languages.
Python conforms to what Martin Fowler calls "Enabling" programming languages. So, if you want to perform modification operations (metaprogramming), why do you have to come up with a "different" or "restrictive" language? Why not just pick up python and start working on it yourself? This is what python decorators can do.
What can you do with Python decorators
Decorators allow you to "inject" or "modify" the code (logic) inside a function or class. In addition to being simpler and more powerful, decorators sound a bit like AOP aspect-oriented programming, right? For example, add something you want to do before the start or end of the method (such as some common operations in aspect-oriented programming such as permission checking, tracking, resource locking, etc.). With decorators, you can do this:
@entryExit def func1(): print "inside func1()" @entryExit def func2(): print "inside func2()"
Function decorator
Functional decorators are usually placed before a function definition code to apply qualified decorators, such as:
@myDecorator def aFunction(): print "inside aFunction"
When the compiler reaches this code, the function aFunction will be compiled, and the compiled function object will be passed to myDecorator. The decorator will generate a new function object to replace the original function aFunction.
So, what is the code implementation of the decorator myDecorator? Although most introductory examples of decorators write a function, I find that class-style decorators are easier to understand and more powerful than functional decorators.
The only thing that needs to be ensured is that the object returned by the decorator can be called like a function, so the class decorator needs to implement __call__.
What should the decorator do? Well, it can do anything, but usually you'd expect the original passed function to be executed somewhere, although this isn't mandatory:
class myDecorator(object): def __init__(self, f): print "inside myDecorator.__init__()" f() # Prove that function definition has completed def __call__(self): print "inside myDecorator.__call__()" @myDecorator def aFunction(): print "inside aFunction()" print "Finished decorating aFunction()" aFunction()
When you execute this code, you will see output like this:
inside myDecorator.__init__() inside aFunction() Finished decorating aFunction() inside myDecorator.__call__()
请注意,myDecorator的构造器实际是在装饰函数的时候执行的。我们可以在__init__()里面调用函数f,能够看到,在装饰器被调用之前,函数调用f()就已经完成了。另外,装饰器的构造器能够接收被装饰的方法。一般来讲,我们会捕捉到这个函数对象然后接下来在函数__call__()里面调用。装饰和调用是两个非常清晰明了的不同的步骤,这也是我为什么说类似装饰器更简单同时也更强大的原因。
当函数aFunction被装饰完成然后调用的时候,我们得到了一个完全不同的行为,实际上执行的是myDecorator.__call__()的代码逻辑,这是因为”装饰“把原有的代码逻辑用新的返回的逻辑给替换掉了。在我们的例子中,myDecorator对象替换掉了函数aFunction。事实上,在装饰器操作符@被加入之前,你不得不做一些比较low的操作来完成同样的事情:
def foo(): pass foo = staticmethod(foo)
因为有了@这个装饰器操作符, 你可以非常优雅的得到同样的结果:
@staticmethod def foo(): pass
不过也有不少人因为这一点反对装饰器,不过@仅仅是一个很小的语法糖而已,把一个函数对象传递给另外一个函数,然后用返回值替换原有的方法。
我觉着,之所以装饰器会产生这么大的影响是因为这个小小的语法糖完全改变了人们思考编程的方式。的确,通过将它实现成一个编程语言结构,它将”代码应用到代码上面“的思想带到了主流编程思维层面。
青出于蓝
现在我们实现一下第一个例子。在这里我们将会做一些很常规的事情,并且会使用这些代码:
class entryExit(object): def __init__(self, f): self.f = f def __call__(self): print "Entering", self.f.__name__ self.f() print "Exited", self.f.__name__ @entryExit def func1(): print "inside func1()" @entryExit def func2(): print "inside func2()" func1() func2()
运行结果是:
Entering func1 inside func1() Exited func1 Entering func2 inside func2() Exited func2
现在我们能够看到,那些被装饰的方法有了“进入”和“离开”的跟踪信息。
构造器存储了通过参数传递进来的函数对象,在调用的方法里,我们用函数对象的__name__属性来展示被调用函数的名称,然后调用被装饰的函数自己。
使用函数作为装饰器
对于装饰器返回结果的约束只有一个,那就是能够被调用,从而它能够合理的替换掉原有的被装饰的那个函数。在上面的这些例子中,我们是将原有的函数用包含有__call__()的对象替换的。一个函数对象同样能够被调用,所以我们可以用函数来重写前一个装饰器的例子,像这样:
def entryExit(f): def new_f(): print "Entering", f.__name__ f() print "Exited", f.__name__ return new_f @entryExit def func1(): print "inside func1()" @entryExit def func2(): print "inside func2()" func1() func2() print func1.__name__
函数new_f()嵌套定义在entryExit的方法体里面,当entryExit被调用的时候,new_f()也会顺理成章地被返回。值得注意的是new_f()是一个闭包,捕获了参数变量f的值。
当new_f()定义完成后,它将会被entryExit返回,然后装饰器机制发生作用将结果赋值成被装饰的新方法。
代码print func1.__name__的输出结果是new_f,因为在装饰发生的过程中,原来的方法已经被替换成了new_f,如果对你来说这是一个问题的话,你可以在装饰器返回结果之前修改掉函数的名字:
def entryExit(f): def new_f(): print "Entering", f.__name__ f() print "Exited", f.__name__ new_f.__name__ = f.__name__ return new_f
你可以动态的获取函数的信息包括那些你做的更改,这在python里面非常有用。
带参数的装饰器
现在我们把上面的那个例子简单的改动一下,看看在添加装饰器参数的情况下会发生什么情况:
class decoratorWithArguments(object): def __init__(self, arg1, arg2, arg3): """ If there are decorator arguments, the function to be decorated is not passed to the constructor! """ print "Inside __init__()" self.arg1 = arg1 self.arg2 = arg2 self.arg3 = arg3 def __call__(self, f): """ If there are decorator arguments, __call__() is only called once, as part of the decoration process! You can only give it a single argument, which is the function object. """ print "Inside __call__()" def wrapped_f(*args): print "Inside wrapped_f()" print "Decorator arguments:", self.arg1, self.arg2, self.arg3 f(*args) print "After f(*args)" return wrapped_f @decoratorWithArguments("hello", "world", 42) def sayHello(a1, a2, a3, a4): print 'sayHello arguments:', a1, a2, a3, a4 print "After decoration" print "Preparing to call sayHello()" sayHello("say", "hello", "argument", "list") print "after first sayHello() call" sayHello("a", "different", "set of", "arguments") print "after second sayHello() call"
从输出结果来看,运行的效果发生了明显的变化:
Inside __init__() Inside __call__() After decoration Preparing to call sayHello() Inside wrapped_f() Decorator arguments: hello world 42 sayHello arguments: say hello argument list After f(*args) after first sayHello() call Inside wrapped_f() Decorator arguments: hello world 42 sayHello arguments: a different set of arguments After f(*args) after second sayHello() call
现在,在“装饰”阶段,构造器和__call__()都会被依次调用,__call__()也只接受一个函数对象类型的参数,而且必须返回一个装饰方法去替换原有的方法,__call__()只会在“装饰”阶段被调用一次,接着返回的装饰方法会被实际用在调用过程中。
尽管这个行为很合理,构造器现在被用来捕捉装饰器的参数,而且__call__()不能再被当做装饰方法,相反要利用它来完成装饰的过程。尽管如此,第一次见到这种与不带参数的装饰器迥然不同的行为还是会让人大吃一惊,而且它们的编程范式也有很大的不同。
带参数的函数式装饰器
最后,让我们看一下更复杂的函数式装饰器,在这里你不得不一次完成所有的事情:
def decoratorFunctionWithArguments(arg1, arg2, arg3): def wrap(f): print "Inside wrap()" def wrapped_f(*args): print "Inside wrapped_f()" print "Decorator arguments:", arg1, arg2, arg3 f(*args) print "After f(*args)" return wrapped_f return wrap @decoratorFunctionWithArguments("hello", "world", 42) def sayHello(a1, a2, a3, a4): print 'sayHello arguments:', a1, a2, a3, a4 print "After decoration" print "Preparing to call sayHello()" sayHello("say", "hello", "argument", "list") print "after first sayHello() call" sayHello("a", "different", "set of", "arguments") print "after second sayHello() call"
输出结果:
Inside wrap() After decoration Preparing to call sayHello() Inside wrapped_f() Decorator arguments: hello world 42 sayHello arguments: say hello argument list After f(*args) after first sayHello() call Inside wrapped_f() Decorator arguments: hello world 42 sayHello arguments: a different set of arguments After f(*args) after second sayHello() call
The return value of a functional decorator must be a function that can wrap the original wrapped function. In other words, Python will get and call the returned function result when decoration occurs, and then pass it to the decorated function. This is why we have three layers of nested functions defined in the implementation of the decorator. That function of the layer is the new replacement function.
Because of the nature of closures, wrapped_f() can access these parameters without explicitly storing the values arg1, arg2, and arg3 like in the class decorator example. However, this happens to be an example where I feel "explicit is better than implicit". Although functional decorators may be a bit more streamlined, class-style decorators are easier to understand and therefore easier to modify and maintain.