【问题标题】:Can I decorate an explicit function call such as np.sqrt()我可以装饰一个显式的函数调用,例如 np.sqrt()
【发布时间】:2014-05-01 06:37:49
【问题描述】:

我对 python 函数装饰器有点了解。我认为我的问题的答案是否定的,但我想确定一下。使用装饰器和 x = np.array([1,2,3]) 的 numpy 数组,我可以覆盖 x.sqrt() 并更改行为。有什么方法可以在 Python 中覆盖 np.sqrt(x)

用例:处理quantities package。希望能够在不更改当前使用 np.sqrt() 的代码库的情况下取不确定数量的平方根。

编辑

我想修改quantities 包中的np.sqrt 以便下面的代码可以工作(所有三个都应该打印相同的结果,注意使用np.sqrt() 时的0 不确定性)。我希望不要求最终用户修改他们的代码,但在quantities 包中正确包装/装饰np.sqrt()。目前,许多 numpy 函数都经过修饰(参见 https://github.com/python-quantities/python-quantities/blob/ca87253a5529c0a6bee37a9f7d576f1b693c0ddd/quantities/quantity.py),但似乎仅在调用 x.func() 时才有效,而不是 numpy.func(x)

import numpy as np
import quantities as pq
x = pq.UncertainQuantity(2, pq.m, 2)
print x.sqrt()
>>> 1.41421356237 m**0.5 +/- 0.707106781187 m**0.5 (1 sigma)
print x**0.5
>>> 1.41421356237 m**0.5 +/- 0.707106781187 m**0.5 (1 sigma)
print np.sqrt(x)
>>> 1.41421356237 m**0.5 +/- 0.0 dimensionless (1 sigma)

【问题讨论】:

  • 装饰器应用于函数定义而不是函数调用。如果函数已经定义,那么使用装饰器就太晚了(尽管您可以通过将包装函数分配给原始名称来实现相同的效果)。你能展示一个实际的代码示例来说明你想要做什么以及想要的效果吗?
  • 提供的示例。我想包装 np.sqrt() (和 np.mean 等),以便它与数量包一起使用。大多数函数都有,但有些没有。

标签: python numpy decorator python-decorators


【解决方案1】:

猴子补丁

如果我正确理解您的情况,您的用例并不是真正的装饰(以标准方式修改 编写的函数) 而是关于猴子补丁: 修改其他人编写的函数而不实际更改该函数定义的源代码。

然后你需要的成语是这样的

import numpy as np  # provide local access to the numpy module object

original_np_sqrt = np.sqrt

def my_improved_np_sqrt(x):
  # do whatever you please, including:
  # - contemplating the UncertainQuantity-ness of x and
  # - calling original_np_sqrt as needed

np.sqrt = my_improved_np_sqrt

当然,这只能改变numpy.sqrt的未来含义, 不是过去的。 因此,如果有人在上述操作之前导入了numpy,并且已经以您希望影响的方式使用了numpy.sqrt,那么您就输了。 (他们映射到的名称 numpy 并不重要。)

但是之后上面的代码被执行了,numpy.sqrt的意思全部 模块(无论是在它之前还是之后导入numpy) 将是 my_improved_np_sqrt 的,无论这些模块的创建者 不管你喜不喜欢(当然,除非更多 numpy.sqrt 的猴子补丁 正在其他地方进行)。

注意

  1. 当你做奇怪的事情时,Python 会变成一个奇怪的平台!
  2. 当你做奇怪的事情时,Python 会变成一个奇怪的平台!
  3. 当你做奇怪的事情时,Python 会变成一个奇怪的平台!

这就是为什么猴子补丁通常不被认为是好的设计风格。 因此,如果您采取这条路线,请确保您非常突出地宣布它 在所有相关文档中。

哦,如果你不想修改其他代码 从您自己的方法直接或间接执行,您可以 引入一个装饰器,在调用之前执行monkeypatching 和 un-monkeypatching(重新分配 original_np_sqrt) 通话后并将该装饰器应用于 你所有的功能有问题。 然后确保在该装饰器中处理异常,以便 un-monkeypatching 在所有情况下都会真正执行。

【讨论】:

  • 嗨@Lutz,谢谢。是的,我想要(ed)修改其他人编写的函数。我注意到您的 note 并且不会这样做。我将习惯于将代码编写为x.mean(),而不是我以前的方式np.mean(x)。只是出于好奇,reload(numpy) 不能处理在我的代码之前加载 numpy 的情况吗?
  • 嗯,Python 有很多内置的东西,但 “一般撤消和重做某些模块所做的一切” 不在其中。 reload 可以改变未来,但不能改变过去。在您的情况下,reload 只会撤消您对numpy.sqrt 的分配,不会神奇地修改其他任何内容。
【解决方案2】:

也许,正如 BrenBarn 所说,

np.sqrt = decorator(np.sqrt)

因为装饰器只是一个可调用对象,它接受一个对象并返回一个修改后的对象。

【讨论】:

  • 我想我已经对装饰师感到头疼了。该软件包包括我还无法理解的装饰代码:github.com/python-quantities/python-quantities/blob/…
  • 我理解你的例子。但是我可以让import pkg 重载np.sqrt 不知道它将如何被调用(即from numpy import *; sqrt();import numpy as n; n.sqrt()。这只有在pkg 导入之后 numpy 时才有效吗?跨度>
  • @mankoff:实际上,只有在 numpy 之前导入pkg 时它才会起作用。
  • @BrenBarn,如何修改尚未导入的内容?即 pkg 如何在不首先导入的情况下更改 np.sqrt ? (没有修改源代码,我认为这是不受欢迎的)
  • @acushner: pkg 必须导入 numpy 并对其进行修改。它必须在 numpy 之前导入,因为它必须是导入 numpy 的第一件事,以确保它所做的任何包装都适用于导入 numpy 的所有其他内容。
猜你喜欢
  • 2014-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-29
  • 2018-10-14
  • 2020-05-20
  • 2014-07-30
  • 2012-10-25
相关资源
最近更新 更多