Understanding Python Decorators

Decorators should be the first problem I encountered when I was learning Python. I read a lot of tutorials at that time, and finally understood and used them at work. This article is stackoverflow : stackoverflow decorators

There are many tutorials on the decorator of great gods on the Internet. Writing this article is to enhance memory and my own understanding.


1. Python everything is an object

This sentence is easy to understand, but it is difficult to understand and use when actually writing code. Since everything in Python is an object, functions can definitely be passed as objects.

Take a chestnut.

# 普通的函数
In[2]: def add(a, b):
  ...:  return a + b
  ...: 
In[3]: add(1, 2)
Out[3]: 3

# 接收函数作为参数
In[3]: def foo(func,a,b):
  ...:  return func(a,b)
  ...: 
In[4]: foo(add,1,2)
Out[4]: 3

# 有一个地方挺有趣的,传参时,为什么传递的是func,而不是直接传递func(),试试看。
In[5]: def bar(func(),a,b):
  File "<ipython-input-5-f09913042048>", line 1
    def bar(func(),a,b):
                ^
SyntaxError: invalid syntax
# 语法错误,无效语法。为什么呢?因为使用func(),是直接执行函数内部的表达式了,这也是func 与func()的区别。

In fact, we usually hear: callback function, which is similar to the above use: function as a parameter.


2. After understanding that everything in Python is an object. Let's look at the decorator.

Decorator: Decorator is a design pattern that is often used in scenarios with aspect requirements, such as log insertion, performance testing, and transaction processing. Decorators are an excellent design to solve such problems. With decorators, we can extract the same code in a large number of functions that has nothing to do with the function itself and continue to reuse it. In a nutshell, the role of a decorator is to add extra functionality to an existing object.

The above is a conceptual explanation, and I understand it in a more general way: it is actually adding some extra functional code before or after or before and after the wrapper function.


@Syntax sugar: Using the @ symbol to decorate a function has the same effect as calling the decorator with the function as a parameter, and then assigning the result returned by the decorator to a variable with the same name as the original function in the same scope → func =deco(func)

  • take a chestnut
def foo():
    print('foo')

# 例如想在foo函数前加上一些功能,但是不能修改foo函数本身的代码。

In[3]: def bar(func):
  ...:  print('start') 
  ...:  func()
  ...:  
In[4]: @bar
  ...: def foo():
  ...:  print('foo')
  ...:  
Out[5]: start
        foo

# 如果想在后面加呢?
In[5]: def bar(func):
  ...:  func()
  ...:  print('end')
  ...:  
In[6]: @bar
  ...: def foo():
  ...:  print('foo')
  ...:  
Out[7]:foo
       end

# 在前后呢?
In[7]: def bar(func):
  ...:  print('start')
  ...:  func()
  ...:  print('end')

In[8]: @bar
  ...: def foo():
  ...:  print('foo')
  ...:  
Out[9]: start
        foo
        end

# 这样就满足了我们不改变原有函数的基础上,添加了额外的功能。
  • After a simple understanding of what's going on with the decorator, we implement a function: performance test
In[3]: import time

# ①
In[4]: def timeit(func):
  ...:  start = time.time()
  ...:  func()
  ...:  end = time.time()
  ...:  print 'cost:{}'.format(end - start)
  ...:  
In[5]: @timeit
  ...: def bar(): #不需要调用bar()
  ...:  sum(range(1000000))
  ...:  

Out[6]:
      cost:0.095999956131

# 这和我们在网上看到的装饰器教程写法不太一样。一般是下面这种写法。

# ②
In[2]: 
  ...: def timeit(func):
  ...:  def wrapper():
  ...:      start = time.time()
  ...:      func()
  ...:      end = time.time()
  ...:      print 'cost:{}'.format(end - start)
  ...:  return wrapper
  ...: 
In[3]: @timeit
  ...: def bar():
  ...:  sum(range(1000000))
  ...:  
In[5]: bar() # 需要调用bar()
Out[6]:
       cost:0.0629999637604   

Analyze the second way of writing

  • The function receives a function func as a parameter.
  • Go down and encounter the wrapper function.
  • Before and after the func() function, execute the time() function to obtain the time.
  • Finally return the embedded function wrapper

    The difference from method 1 is that method 2 returns a new function with additional functions after wrapping the function func. When we call the bar() function, we can loosely understand that the wrapper() function is called (in fact, The new function name is still bar), because of the syntactic sugar of @, timeit(bar) returns a wrapper function object, which is passed to bar, which is equivalent to bar = timeit(bar),), so that the new function can be used in any place to be called.
    In method 1, the expression in timeit is directly executed, and a function object cannot be returned. So we generally use the second way so that we can call new functions anytime and anywhere.

3. Extension of the decorator

  • function decorated with parameters
In[6]: def deco(func):
  ...:  def wrapper(a,b):
  ...:      print 'start'
  ...:      result = func(a, b)
  ...:      print 'end'
  ...:      return result
  ...:  return wrapper
  ...: 
In[7]: @deco
  ...: def add(a,b):
  ...:  return a + b
  ...: 
In[8]: add(1,2)
Out[8]: 
        start
        end
        3
  • Decorate a function with indefinite arguments

In[11]: def deco(func):
   ...:     def wrapper(*args, **kwargs):
   ...:         result = func(*args, **kwargs)
   ...:         return result
   ...:     return wrapper
  • decorator with parameters
# 在最外层包装了一层函数,用来接收参数。

In[13]: def deco(var):
   ...:     print var
   ...:     def _deco(func):
   ...:         def wrapper():
   ...:             func()
   ...:         return wrapper
   ...:     return _deco
  • decorator decorator
# 典型例子就是单例模式。

In[14]: def singleton(cls):
   ...:     instance = {}
   ...: 
   ...:     def wrapper(*args, **kwargs):
   ...:         if cls not in instance:
   ...:             instance[cls] = cls(*args, **kwargs)
   ...:         return instance[cls]
   ...: 
   ...:     return wrapper
   ...: 
   ...: 
   ...: @singleton
   ...: class Foo(object):
   ...:     pass
  • Decorators decorate class methods
In[7]: def deco(func):
  ...:  def wrapper(self):
  ...:      print 'wrapper'
  ...:      func(self)
  ...:  return wrapper
  ...: 
In[8]: class MyClass(object):
  ...:  @deco
  ...:  def foo(self):
  ...:      print 'foo'
  ...:      
In[9]: mycls = MyClass()
In[10]: mycls.foo()
Out[11]:
        wrapper
        foo
  • class decorator decorator function

In[15]: class Foo(object):
   ...:     def __init__(self, func):
   ...:         self.func = func
   ...: 
   ...:     def __call__(self, *args, **kwargs):
   ...:         print 'start'
   ...:         self.func(*args, **kwargs)
   ...:         print 'end'
   ...: 
   ...: 
   ...: @Foo
   ...: def bar():
   ...:     print 'bar'
  • Class decorators take parameters, decorate functions
In[7]: class Bar(object):
  ...:  def __init__(self, arg):
  ...:      self.arg = arg
  ...:  def __call__(self,func , *args, **kwargs):
  ...:      print self.arg
  ...:      print 'start'
  ...:      func(*args ,**kwargs)
  ...:      print 'end'
  ...:      
In[8]: @Bar('xwm')
  ...: def foo():
  ...:  print 'foo'
  ...: 
Out[9]: 
        xwm
        start
        foo
        end
  • Nested decorators
In[2]: def deco1(func):
  ...:  def wrapper():
  ...:      print 'deco1'
  ...:      func()
  ...:      print 'deco1'
  ...:  return wrapper
  ...: 

In[4]: def deco2(func):
  ...:  def wrapper():
  ...:      print 'deco2'
  ...:      func()
  ...:      print 'deco2'
  ...:  return wrapper
  ...: 

In[5]: def deco3(func):
  ...:  def wrapper():
  ...:      print 'deco3'
  ...:      func()
  ...:      print 'deco3'
  ...:  return wrapper
  ...: 
In[6]: 
In[6]: @deco1
  ...: @deco2
  ...: @deco3
  ...: def foo():
  ...:  print 'foo'
  ...:  
In[7]: foo()
Out[8]:
        deco1
        deco2
        deco3
        foo
        deco3
        deco2
        deco1
# 执行顺序从里到外,先调用最里层的装饰器,依次往外层调用装饰器 deco3→deco2→deco1

PS: Generally, in order to retain the meta information of the decorated function, it is necessary to combine the wraps of the functools module. Wraps itself is also a decorator, which retains the meta function information.

  • Decorator saves meta information
In[8]: from functools import wraps
In[9]: def deco(func):
   ...:     @wraps
   ...:     def wrapper():
   ...:         func()
   ...:     return wrapper  

In fact, it is difficult to understand the nested functions inside the function, because the human brain is used to thinking linearly, you can consider, the def wrapp(): This code in it disappears directly. Then look at the overall code.

def deco3(func):
    # def wrapper():   想象不存在这行代码,最后这个函数deco3返回的是一个含有被装饰的函数foo()功能的新函数,并且新函数的名字还是被装饰的函数foo().
        print 'deco3'
        func()
        print 'deco3'
    return wrapper

The above is my own understanding of the decorator. After understanding it, I will often use it in my work. As for the effect, whoever uses it knows, ^_^

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325384464&siteId=291194637