Python之functools.wraps

在看 Bottle 代碼中看見 functools.wraps 這種用法。python

def make_default_app_wrapper(name):
    """ Return a callable that relays calls to the current default app. """
    a = getattr(Bottle, name)
    @functools.wraps(getattr(Bottle, name))
    def wrapper(*a, **ka):
        return getattr(app(), name)(*a, **ka)
    return wrapper

以前沒有看過,因而查文檔瞭解了一下他的用處
先下定義:
functools.wraps 是 裝飾器裝飾器app

要明白 functiools.wraps 首先要明白 Python 的 Decorator函數

Decorator

在之前的 Blog 中曾經簡單寫過 Decorator。此次須要講的更細一些。code

Decorator 經過返回包裝對象實現間接調用,以此插入額外邏輯。是從老大那邊偷來的哪裏摘抄來的,應該算是言簡意賅了。對象

@dec2
@dec1
def func(arg1, arg2, ...):
    pass

能夠還原成three

def func(arg1, arg2, ...):
    pass
func = dec2(dec1(func))

```python
@decomaker(argA, argB, ...)
def func(arg1, arg2, ...):
pass文檔

能夠還原成
```python
func = decomaker(argA, argB, ...)(func)
In [1]: def outer(func):
   ...:     def inner():
   ...:         print "before func"
   ...:         ret = func()
   ...:         return ret + 1
   ...:     return inner #返回 inner 函數對象
   ...:

In [2]: @outer  # 解釋器執⾏行 foo = outer(foo)
   ...: def foo():
   ...:     return 1
   ...:

In [3]: foo
Out[3]: <function __main__.inner>

In [4]: foo()
        before func
Out[4]: 2

這個過程當中執行了下面幾步get

  1. 函數 foo 做爲 裝飾器 outer 的參數被傳入
  2. 函數 inner 對 func 進行調用,而後裝飾器 outer 返回 inner
  3. 原來的函數名 foo 關聯到 inner,如上面的foo <function __main__.inner> 所示,調用 foo 時間上是在調用 inner

裝飾器不只能夠用函數返回包裝對象,也能夠是個類,不過這種方法太尼瑪囉嗦,這裏就不介紹了,想了解的本身去翻吧。下面咱們寫一個有點用處的 Decorator。
假想咱們有個coordinate類,並且這個類提供了 x, y座標,而咱們要對兩個coordinate 對象進行計算。代碼以下:it

class Coordinate(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __repr__(self):
        return "Coord: " + str(self.__dict__)

def add(a, b):
    return Coordinate(a.x + b.x, a.y + b.y)

def sub(a, b):
    return Coordinate(a.x - b.x, a.y - b.y)

In [8]: one = Coordinate(100, 200)

In [9]: two = Coordinate(300, 200)

In [10]: three = Coordinate(-100, -100)

In [11]: sub(one, three)
Out[11]: Coord: {'y': 300, 'x': 200}

In [12]: add(one, three)
Out[12]: Coord: {'y': 100, 'x': 0}

In [13]: sub(one, two)
Out[13]: Coord: {'y': 0, 'x': -200}

上面例子中的sub(one, two)three都有負數,當咱們把座標限制在第一象限時,這兩個就不符合咱們的要求,用 Decorator 來作一個檢測再好不過了io

In [14]: def wrapper(func):
   ....:     def checker(a, b):
   ....:         if a.x < 0 or a.y < 0:
   ....:             a = Coordinate(a.x if a.x > 0 else 0, a.y if a.y > 0 else 0)
   ....:         if b.x < 0 or b.y < 0:
   ....:             b = Coordinate(b.x if b.x > 0 else 0, b.y if b.y > 0 else 0)
   ....:         ret = func(a, b)
   ....:         if ret.x < 0 or ret.y <0:
   ....:             ret = Coordinate(ret.x if ret.x > 0 else 0, ret.y if ret.y > 0 else 0)
   ....:         return ret
   ....:     return checker
   ....:
In [16]: @wrapper
   ....: def add(a, b):
   ....:     return Coordinate(a.x + b.x, a.y + b.y)
   ....:

In [17]: @wrapper
   ....: def sub(a, b):
   ....:     return Coordinate(a.x - b.x, a.y + b.y)
   ....:

In [18]: add(one, three)
Out[18]: Coord: {'y': 200, 'x': 100}

In [19]: one
Out[19]: Coord: {'y': 200, 'x': 100}

In [20]: sub(one, two)
Out[20]: Coord: {'y': 400, 'x': 0}

這樣,只計算的函數addsub前面加一個 Decorator 就能夠完成座標的校驗。比在函數內實現要優雅一些。

Decorator 還能夠爲類增長額外的成員,

In [21]: def hello(cls):
   ....:     cls.hello = staticmethod(lambda: "HELLO")
   ....:     return cls
   ....:

In [22]: @hello
   ....: class World(object):pass
   ....:

In [23]: World.hello
Out[23]: <function __main__.<lambda>>

In [24]: World.hello()
Out[24]: 'HELLO'

functools.wraps

咱們在使用 Decorator 的過程當中,不免會損失一些本來的功能信息。直接拿 stackoverflow 裏面的栗子

def logged(func):
    def with_logging(*args, **kwargs):
        print func.__name__ + " was called"
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
   """does some math"""
   return x + x * x

def f(x):
    """does some math"""
    return x + x * x
f = logged(f)

In [24]: f.__name__
Out[24]: with_logging

而functools.wraps 則能夠將原函數對象的指定屬性複製給包裝函數對象, 默認有 __module____name____doc__,或者經過參數選擇。代碼以下:

from functools import wraps
def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print func.__name__ + " was called"
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
   """does some math"""
   return x + x * x

print f.__name__  # prints 'f'
print f.__doc__   # prints 'does some math'
相關文章
相關標籤/搜索