详解Python装饰器执行顺序迷思

  


  

  

装饰器是Python用于封装函数或代码的工具,网上可以搜到很多文章可以学习,我在这里要讨论的是多个装饰器执行顺序的一个迷思。

  


  

  

大部分涉及多个装饰器装饰的函数调用顺序时都会说明它们是自上而下的,比如下面这个例子:

        def decorator_a(函数):   打印“进入decorator_a”   def inner_a (* args, * * kwargs):   打印“进入inner_a”   返回func (* args, * * kwargs)   返回inner_a      def decorator_b(函数):   打印“进入decorator_b”   def inner_b (* args, * * kwargs):   打印“进入inner_b”   返回func (* args, * * kwargs)   返回inner_b      @decorator_b   @decorator_a   def f (x):   打印的得到f '   返回x * 2      f (1)      

上面代码先定义里两个函数:decotator_a, decotator_b,这两个函数实现的功能,是接收一个函数作为参数然后返回创建的另一个函数,在这个创建的函数里调用接收的函数(文字比代码绕人)。最后定义的函数f采用上面定义的decotator_a, decotator_b作为装饰函数。在当我们以1为参数调用装饰后的函数f后,decotator_a, decotator_b的顺序是什么呢(这里为了表示函数执行的先后顺序,采用打印输出的方式来查看函数的执行顺序)?

  

如果不假思索根据自下而上的原则来判断地话,先执行decorator_a再执行decorator_b,那么会先输出decotator_a,进入inner_a再输出decotator_b, inner_b。然而事实并非如此。

  

实际上运行的结果如下:

  
  

进入decorator_a
  进入decorator_b
  进入inner_b
  进入inner_a
  在f
  

     


  

  

为什么是先执行inner_b再执行inner_a呢?为了彻底看清上面的问题,得先分清两个概念:函数和函数调用。上面的例子中f称之为函数,f(1)称之为函数调用,后者是对前者传入参数进行求值的结果。在Python中函数也是一个对象,所以f是指代一个函数对象,它的值是函数本身,f(1)是对函数的调用,它的值是调用的结果,这里的定义下f(1)的值2。同样地,拿上面的decorator_a函数来说,它返回的是个函数对象inner_a,这个函数对象是它内部定义的。在inner_a里调用了函数函数,将函数的调用结果作为值返回。

  


  

  

其次得理清的一个问题是,当装饰器装饰一个函数时,究竟发生了什么。现在简化我们的例子,假设是下面这样的:

        def decorator_a(函数):   打印“进入decorator_a”   def inner_a (* args, * * kwargs):   打印“进入inner_a”   返回func (* args, * * kwargs)   返回inner_a      @decorator_a   def f (x):   打印的得到f '   返回x * 2      之前      

正如很多介绍装饰器的文章里所说:

        @decorator_a   def f (x):   打印的得到f '   返回x * 2      #相当于   def f (x):   打印的得到f '   返回x * 2      f=decorator_a (f)      之前      

所以,当解释器执行这段代码时,decorator_a已经调用了,它以函数f作为参数,返回它内部生成的一个函数,所以此后f指代的是decorater_a里面返回的inner_a。所以当以后调用f时,实际上相当于调用inner_a,传给f的参数会传给inner_a,在调用inner_a时会把接收到的参数传给inner_a里的函数即f,最后返回的是f调用的值,所以在最外面看起来就像直接再调用f一样。

  


  

  

当理清上面两方面概念时,就可以清楚地看清最原始的例子中发生了什么。
  

  

当解释器执行下面这段代码时,实际上按照从下到上的顺序已经依次调用了decorator_a和decorator_b,这是会输出对应的进入decorator_a和decorator_b。这时候f已经相当于decorator_b里的inner_b。但因为f并没有被调用,所以inner_b并没有调用,依次类推inner_b内部的inner_a也没有调用,所以进入inner_a和进入inner_b也不会被输出。

        @decorator_b   @decorator_a   def f (x):   打印的得到f '   返回x * 2   之前      

然后最后一行当我们对f传入参数1进行调用时,inner_b被调用了,它会先打印inner_b,然后在inner_b内部调用了inner_a所以会再打印inner_a,然后再inner_a内部调用的原来的f,并且将结果作为最终的返回。这时候你该知道为什么输出结果会是那样,以及对装饰器执行顺序实际发生了什么有一定了解了吧。

详解Python装饰器执行顺序迷思