【问题标题】:Accessing to a nonlocal variable by the following limitations [closed]通过以下限制访问非局部变量[关闭]
【发布时间】:2013-11-07 01:17:37
【问题描述】:

我正在尝试找出 Python 练习中的情况
问题是:

定义两个函数:

  • p:打印变量的值
  • q:增加变量
  • 变量的初始值为0

限制:

  • 变量不在全局环境中,唯一的方法 改变它是通过调用 q()
  • 代码引入全局环境的唯一绑定是针对 p 和 q。

下面的代码将描述这种情况:

# >>> p()
# 0
# >>> q()
# >>> q()
# >>> p()
# 2
# >>> print([k for k,v in globals().items() if v==2]) 
## checks that a variable with the value ‘2’ does not exist in the global environment.
# [] 

我想得到一些建议,我应该如何解决它。
谢谢。

【问题讨论】:

  • 把你的问题弄清楚。
  • 值加 1 的全局变量将适合测试...

标签: python function increment


【解决方案1】:

您可以在函数的局部范围内定义变量:

def p():
    i = [0]
    def p():
        return i[0]
    def q():
        i[0] += 1
    return p, q

p, q = p()

print(p())
# 0
q()
q()
print(p())
# 2
print([k for k,v in globals().items() if v==2]) 
# []

在 Python3 中,您可以使用 nonlocal 语句,这样您就可以将 i 设为 int 而不是列表:

def p():
    i = 0
    def p():
        return i
    def q():
        nonlocal i
        i += 1
    return p, q

【讨论】:

  • 我也有这个想法,但是 OP 指定了 Variable is not located in the global environment, and the only way to change it is by invoking q(),我认为您的 p() 不符合该合同。
  • 好点。我将发布一个不同的解决方案来解决这一点。
  • 但 make_pq 仍在全局变量中...(“代码引入全局环境的唯一绑定是 p 和 q”)
  • 在您的第一个解决方案中,您需要添加del make_pq 以满足不向全局环境添加其他名称的要求。或者直接将其命名为 pq 并覆盖它。
  • 感谢@Duncan 和 Don 的更正。
【解决方案2】:

免责声明:这不是一个非常严肃的答案。不过符合 OP 中规定的规格!

def p():
    print(p.__dict__.get('smuggled_value',0))

def q():
    p.smuggled_value = getattr(p,'smuggled_value',0) + 1

更严肃的回答:如果您允许 pq 有论据,这个问题就很简单了。否则,您需要做一些非常 hacky 的事情,或者使用全局变量。

编辑:按要求解释:

在python中,一切都是对象,包括函数。具有__dict__ 属性的对象允许使用object.attribute 语法进行属性分配。通常你只对class 成员进行这种属性分配,但函数也允许这样做。所以我利用了这一点,因为你只能创建 pq

请注意,您实际上不应该这样编写代码。从您的困惑中可以看出,这种编码方式无法可读。为您的函数提供参数,以便它们可读。

【讨论】:

  • 是的,我知道如果没有作弊,这真的很难做到。无论如何,您的回答很有用并解决了问题。但是如果你能解释一下它是如何工作的,因为我不知道你使用的那些功能。谢谢
  • @RefaelOknin 我在解释中进行了编辑。
【解决方案3】:

这是一个基于闭包的版本,不会将pq 以外的任何符号引入全局范围:

@apply
def p():
    x = [0]
    def p():
        print x[0]
    return p

def q():
    p.func_closure[0].cell_contents[0] += 1

这绝对是“hacky”方面而不是“严肃回答”方面,但另一方面,您的要求也是如此。 ;-)


我还要说,严格满足这个要求是不可能的……

改变它的唯一方法是调用 q()

因为在此示例中,您可以手动执行 q 在内部执行的操作。

(或者你可以利用解释器的实现细节,声明一个ctypes 指针,甚至在内存级别上进行更糟糕的黑客攻击......)

【讨论】:

    【解决方案4】:

    也许 q 可以在每次调用时替换 p 函数:

    p = lambda: 0
    
    def q():
        global p
        value = p() + 1
        p = lambda: value
    

    (虽然它并不完全符合规范——实际上没有任何变量被递增)

    【讨论】:

      【解决方案5】:

      由于到目前为止没有人发布基于类的解决方案:

      class _pq(object):
          _x = 0
          @classmethod
          def p(cls):
              print cls._x
          @classmethod
          def q(cls):
              cls._x += 1
      
      p = _pq.p
      q = _pq.q
      del _pq
      

      【讨论】:

        【解决方案6】:

        知道了,我的最终答案。仅将 p 和 q 引入命名空间,从不更改它们。该变量位于 lambda 的默认参数中,该参数仅被调用一次,没有参数,并返回函数。我们避免使用 __import__ 将运算符放入全局命名空间(对于 operator.setitem)。

        p, q = (lambda i=[0]: 
            (lambda: i[0], 
             lambda: __import__('operator').setitem(i, 0, i[0]+1))
        )()
        

        我认为这个解决方案最适合这个问题,例如除了调用 q 之外,没有其他方法可以更改变量,并且它不会在全局命名空间中引入任何不必要的东西。 虽然感觉更像 Javascript 而不是 Python :-)

        【讨论】:

        • "除了调用 q 没有办法改变变量" ?好吧,实际上有 - 试试这个:q.func_closure[0].cell_contents[0] += 42; print p();。实际上p() 应该打印这个值,而不是返回它。
        • 好的,我不知道可以关闭函数,谢谢。打印并不比返回更难,因此可以留给读者作为练习。我仍然认为这个解决方案是最干净的...... :-)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-04-12
        • 1970-01-01
        • 2018-06-01
        • 2018-12-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多