【问题标题】:How to force python3 to pass by value?如何强制python3按值传递?
【发布时间】:2012-04-06 10:46:15
【问题描述】:

我的目的是创建一个字典,其中的键是原语,其值是返回字符串的零参数函数。 (这是实现 VM 的更大项目的一部分。)其中某些功能非常重要,需要手动创建和分配。那些工作正常。然而,其他的似乎适合自动生成。

我的第一次尝试失败了:

>>> regs = ['a', 'b', 'c', 'x', 'y', 'z']
>>> vals = {i : lambda: r for i, r in enumerate(regs)}
>>> [(k, vals[k]()) for k in vals.keys()]
[(0, 'z'), (1, 'z'), (2, 'z'), (3, 'z'), (4, 'z'), (5, 'z')]

好的,好的; lambda 函数在被调用之前不会读取 r。我又试了一次,试图自己隔离一个值:

>>> from copy import copy
>>> vals = {}
>>> i = 0
>>> for reg in regs:
...     r = copy(reg)  # (1)
...     vals[i] = lambda: r
...     i += 1
...
>>> [(k, vals[k]()) for k in vals.keys()]
[(0, 'z'), (1, 'z'), (2, 'z'), (3, 'z'), (4, 'z'), (5, 'z')]

(1) 我认为这一步会创建一个在 reg 发生时不会改变的自变量。事实证明并非如此。

所以这种尝试显然没有奏效。也许复制,在一个字符串上,是一个 noop?

>>> 's' is 's'
True
>>> a = 's'
>>> b = copy(a)
>>> a is b
True
>>> from copy import deepcopy
>>> b = deepcopy(a)
>>> a is b
True

没错。复制,在一个字符串上,是一个 noop。 Deepcopy 不能解决这个问题。因此,lambda 仍然引用了在每个循环中更新的变量,从而导致了这个错误。

我们需要一种不同的方法。如果我将我想要的变量保存到临时函数的静态变量中怎么办?如果每个临时函数都有自己的身份,那应该可以工作......

>>> vals = {}
>>> i = 0
>>> for reg in regs:
...     def t():
...             return t.r
...     t.r = reg
...     vals[i] = t
...     i += 1
...
>>> [(k, vals[k]()) for k in vals.keys()]
[(0, 'z'), (1, 'z'), (2, 'z'), (3, 'z'), (4, 'z'), (5, 'z')]

不。在这一点上,我即将手动处理这一切:

>>> vals = {}
>>> vals[0] = lambda: 'a'
>>> vals[1] = lambda: 'b'

...等等。然而,这感觉就像放弃,而且会非常乏味。有没有合适的 Pythonic 方法来完成这项任务?毕竟,我通常喜欢 python 的原因之一是我可以远离手动指针管理;我从没想过我会希望它包含一整套指针工具!

【问题讨论】:

    标签: lambda python-3.x immutability


    【解决方案1】:

    闭包从不复制,它们既不复制值也不复制引用。相反,他们记住了他们使用的范围和变量,并且总是回到那里。在其他几种语言中也是如此,例如 C#、JavaScript、IIRC Lisp 等等。在其他几种语言中也是如此。这对于一些高级用例很重要(基本上每次你想要几个闭包共享状态时),但它可能会咬一个。例如:

    x = 1
    def f(): return x
    x = 2
    assert f() == 2
    

    由于 Python 只为函数(以及类和模块,但这并不重要)创建新的作用域,循环变量 reg 只存在一次,因此所有闭包都引用同一个变量。因此,在循环之后调用时,它们会看到变量假定的最后一个值。

    同样如此,只是这次是共享的t——你确实创建了N个单独的闭包,每个闭包在其r属性中都有正确的值。但是当被调用时,它们会在封闭范围内查找 t,因此总是会获得对您创建的最后一个闭包的引用。

    有几种解决方法。一个是将闭包创建推入一个单独的函数中,这会强制每个闭包引用一个新的专用范围:

    def make_const(x):
        def const():
            return x
        return const
    

    另一种可能性(更简洁但更晦涩)是(ab-)使用默认参数在定义时绑定的事实:

    for reg in regs:
        t = lambda reg=reg: reg
    

    在其他情况下,你可以使用functools,但在这里似乎并不适用。

    【讨论】:

    • 谢谢!设置默认参数对我来说比通过 make_const 函数提供所有内容更有意义,至少就我对其工作原理的理解而言。我认为这两种方法在性能上没有任何显着差异?
    • @coriolinus 应该没有任何显着差异。如果有的话,在成为瓶颈之前可能还有很多更大的鱼要炸。到那时,您可能应该咬紧牙关,将您的代码设为RPython,然后编译为 C、JIT 编译器和一系列几乎免费的优质 GC。
    猜你喜欢
    • 1970-01-01
    • 2013-01-21
    • 1970-01-01
    • 2013-02-09
    • 2013-05-03
    • 2021-11-04
    • 2023-03-17
    • 2011-11-24
    • 2018-07-13
    相关资源
    最近更新 更多