Python之functools.wraps
admin
2023-07-31 01:49:06
0

在看 Bottle 代码中看见 functools.wraps 这种用法。

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 是 装饰器装饰器

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

Decorator

在以前的 Blog 中曾经简单写过 Decorator。这次需要讲的更细一些。

Decorator 通过返回包装对象实现间接调用,以此插入额外逻辑。是从老大那边偷来的哪里摘抄来的,应该算是言简意赅了。

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

可以还原成

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]: 

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

这个过程中执行了下面几步

  1. 函数 foo 作为 装饰器 outer 的参数被传入
  2. 函数 inner 对 func 进行调用,然后装饰器 outer 返回 inner
  3. 原来的函数名 foo 关联到 inner,如上面的foo 所示,调用 foo 时间上是在调用 inner

装饰器不仅可以用函数返回包装对象,也可以是个类,不过这种方法太尼玛啰嗦,这里就不介绍了,想了解的自己去翻吧。下面我们写一个有点用处的 Decorator。
假想我们有个coordinate类,而且这个类提供了 x, y坐标,而我们要对两个coordinate 对象进行计算。代码如下:

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 来做一个检测再好不过了

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]: >

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\'

相关内容

热门资讯

Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...
65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
Apache Doris 2.... 亲爱的社区小伙伴们,我们很高兴地向大家宣布,Apache Doris 2.0.0 版本已于...
项目管理和工程管理的区别 项目管理 项目管理,顾名思义就是专注于开发和完成项目的管理,以实现目标并满足成功标准和项目要求。 工...