【问题标题】:Can I use a decorator to mutate the local scope of a function in Python?我可以使用装饰器来改变 Python 中函数的本地范围吗?
【发布时间】:2010-10-10 02:58:48
【问题描述】:

有没有什么方法可以编写装饰器以使以下内容起作用?

assert 'z' not in globals()

@my_decorator
def func(x, y):
   print z

编辑:从 anwser 移出

回答 hop 的“为什么?”:语法糖 / DRY。

这不是关于缓存,而是关于根据 x 和 y 的值计算 z(以及 z1、z2、z3...)。

我有很多函数做相关的事情,我不想写

z1, z2, z3=calculate_from(x, y)

在每个函数的开头 - 我会在某个地方弄错。如果这是 c,我会用 cpp 来做这个(如果这是 lisp,我会用宏来做这个......),但我想看看装饰器是否可以做同样的事情。

如果有帮助,我几乎可以肯定将装饰器称为“precalculate_z”,它肯定不会成为任何公共 API 的一部分。

我可能也可以通过使用类基础结构获得类似的效果,但我想看看它是否可以使用原始函数。

【问题讨论】:

  • 我不确定您要在这里完成什么 — 如果 z 既不是全局参数也不是函数参数,它来自哪里?或者您想让z 静态化? Python 不支持 C 风格的静态变量,但您可以通过将函数放入类中来完成很多相同的事情。
  • 我相信他在问“my_decorator”是否可以将局部变量注入它正在装饰的函数体中。

标签: python decorator


【解决方案1】:

呼应 Hop 的回答

  1. 不要这样做。
  2. 说真的,不要这样做。 Lisp 和 Ruby 是更适合编写您自己的自定义语法的语言。使用其中之一。或者找到一种更简洁的方法来做到这一点
  3. 如果必须,您需要动态范围的变量,而不是词法范围的变量。

Python 没有动态范围的变量,但您可以模拟它。这是一个通过创建全局绑定来模拟它的示例,但在退出时恢复以前的值:

http://codepad.org/6vAY8Leh

def adds_dynamic_z_decorator(f):
  def replacement(*arg,**karg):
    # create a new 'z' binding in globals, saving previous
    if 'z' in globals():
      oldZ = (globals()['z'],)
    else:
      oldZ = None
    try:
      globals()['z'] = None
      #invoke the original function
      res = f(*arg, **karg)
    finally:
      #restore any old bindings
      if oldZ:
        globals()['z'] = oldZ[0]
      else:
        del(globals()['z'])
    return res
  return replacement

@adds_dynamic_z_decorator
def func(x,y):
  print z

def other_recurse(x):
  global z
  print 'x=%s, z=%s' %(x,z)
  recurse(x+1)
  print 'x=%s, z=%s' %(x,z)

@adds_dynamic_z_decorator
def recurse(x=0):
  global z
  z = x
  if x < 3:
    other_recurse(x)

print 'calling func(1,2)'
func(1,2)

print 'calling recurse()'
recurse()

我对上述代码的实用性或完整性不做任何保证。实际上,我保证它疯狂的,除非你想受到 Python 同行的鞭挞,否则你应该避免使用它。

此代码类似于 eduffy 和 John Montgomery 的代码,但确保“z”被创建并正确恢复,“就像”局部变量一样——例如,请注意“other_recurse”如何能够看到绑定对于“递归”主体中指定的“z”。

【讨论】:

  • 谢谢 - 这会做我想要的;但我肯定不会使用它。但至少现在我的好奇心得到了满足!我仍然想要一种方法来避免我的复制/粘贴问题,但我不确定如何 - 我会再玩一些。
  • 很有趣,但我想知道在 Python 中使用这个会破坏什么?
  • 如果你决定使用它(尽管我同意“不要这样做”的说法),这个实现中有一个小错误。如果 z 存在于全局范围内,并且计算结果为 False(False、None、空字符串等),它将被此函数删除。我会使用一个单独的布尔标志来确定是否应该恢复“z”。
  • 另外,我认为这里有一个多线程错误(如果其他线程使用这个全局,它可能会意外地改变它们),但我不知道如何解决它。跨度>
  • @oren - 嗯,我的意思是这样做,以确保只有专家才能正确使用代码? :-)
【解决方案2】:

我不知道本地范围,但您可以临时提供一个替代的全局名称空间。比如:

import types def my_decorator(fn): def decorated(*args,**kw): my_globals={} my_globals.update(globals()) my_globals['z']='value of z' call_fn=types.FunctionType(fn.func_code,my_globals) return call_fn(*args,**kw) return decorated @my_decorator def func(x, y): print z func(0,1)

应该打印“z 的值”

【讨论】:

  • 这不会在定义 func 时将 my_globals 的状态冻结为 globals 的状态吗?
  • 不,my_globals 会在每次调用 decorated 时重新创建(实际函数由 decorated 返回)。这应该意味着您始终拥有最新的全局变量。
  • 哦,对,当然。看到但忽略了嵌套函数。演示了为什么应该避免这种情况。
  • 嗯,它只是一个相当标准的装饰器,但是像这样的全局变量可能应该避免通常。但是我可以想象在某些情况下知道 如何 会很有用(即使您以后追求更好的方法)。
  • 嘿——我找到它的时候就是这样 ;^)
【解决方案3】:

a) 不要这样做。

b) 说真的,你为什么要那样做?

c) 你可以在你的装饰器中将 z 声明为全局的,所以在装饰器第一次被调用之前 z 不会在 globals() 中,所以断言不会吠叫。

d) 为什么???

【讨论】:

  • +1:如果你想要一个有“内存”或“缓存”的函数,定义一个可调用对象,使用 call_ 方法。
  • 说真的,你为什么要这么做?您需要 Common Lisp 及其扩展语言的能力。或者也许 Ruby 可以做到这一点。 Python 对很多事情都有好处,但不是这个。
  • 如果在全局 z 声明后重新加载该代码,断言会中断吗?
【解决方案4】:

我将首先回应“请不要”,但这是您的选择。这里有一个解决方案:

assert 'z' not in globals ()

class my_dec:
    def __init__ (self, f):
        self.f = f
    def __call__ (self,x,y):
        z = x+y
        self.f(x,y,z)

@my_dec
def func (x,y,z):
    print z

func (1,3)

它在形式参数中确实需要z,但不是实际参数。

【讨论】:

    【解决方案5】:

    我可能也可以通过使用类基础结构获得类似的效果,但我想看看它是否可以使用原始函数。

    嗯,Python 是一种面向对象的语言。在我看来,你应该在课堂上这样做。制作一个漂亮的类接口肯定会简化你的问题。这不是装饰器的用途。

    【讨论】:

      【解决方案6】:

      显式优于隐式。

      这够好吗?

      def provide_value(f):
          f.foo = "Bar"
          return f
      
      @provide_value
      def g(x):
          print g.foo
      

      (如果你真的想要邪恶,分配给 f.func_globals 似乎很有趣。)

      【讨论】:

      • 这就是我大致开始的地方 - 但如果我在十个函数中执行此操作(每个函数都有不同的名称),那么我的 foo 被命名为 g.foo、h.foo、i.foo、 ...在每个功能中。我只想谈谈foo。
      【解决方案7】:

      其他人提供了几种制作工作装饰器的方法,但许多人建议不要这样做,因为它在风格上与正常的 Python 行为如此不同,以至于任何试图理解代码的人都会感到困惑。

      如果您需要大量重新计算事物,将它们组合在一个对象中是否有意义?在构造函数中计算 z1...zN,然后使用这些值的函数可以访问预先计算的答案作为实例的一部分。

      【讨论】:

        猜你喜欢
        • 2020-02-12
        • 1970-01-01
        • 2012-08-18
        • 1970-01-01
        • 2015-09-29
        • 2019-02-02
        • 2020-05-20
        • 2022-06-11
        • 2011-02-12
        相关资源
        最近更新 更多