在看 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
函數
在之前的 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
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}
這樣,只計算的函數add
與sub
前面加一個 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'
咱們在使用 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'