【问题标题】:Python: unwanted change of variable occurs [duplicate]Python:发生不必要的变量更改[重复]
【发布时间】:2011-12-16 13:37:23
【问题描述】:

可能重复:
“Least Astonishment” in Python: The Mutable Default Argument

好的,所以基本上我有这个代码:

 # Our Rule type.

class Rule(object):
    """An object to represent a rule and include
    methods to apply these rules."""

    def __init__(self,bind="F",rule=["F"]):
        self.bind = bind
        self.rule = rule

    def show(self):
        try:
            print self.bind
            #print self.rule
            for i in range(len(self.rule)):
                print self.rule[i]
        except:
            print "Incomplete"

    def inflate(self,seq):
        for i in range(len(seq)):
            if seq[i] == self.bind:
                seq[i] = self.rule
            elif type(seq) == type(["X"]):
                seq[i] = self.inflate(seq[i])
        return seq

    def inflate_depth(self,seq,depth):
        while depth > 0:
            seq = self.inflate(seq)
            depth = depth - 1
            print self.rule
        return seq

我用这个代码从另一个文件调用它:

pity = Rule(rule=["R","F","L","F","L","F","R","F"])

seq = ["F"]

inf = pity.inflate_depth(seq,2)

所以,我最终应该得到一个如下所示的列表:

[['R', [...], 'L', [...], 'L', [...], 'R', [...]]]

这似乎工作正常,但有一个基本错误。

self.rule

已更改为包含['R', [...], 'L', [...], 'L', [...], 'R', [...]]

为什么?没有任何地方可以为该变量分配新值,但它仍然会发生变化。

【问题讨论】:

  • @delnan:在这种情况下,最直接的问题不是默认参数,而是在需要属性值的副本时传递对属性的引用(尽管可变的默认参数也是一个潜在的问题)。

标签: python class list


【解决方案1】:

默认参数在定义函数时计算一次,以后再使用。

def f(x, a = [])
    a.append(x)
    return a
print f(0)
print f(1)

打印

[0]
[0, 1]

避免这个问题的常用方法是使用None作为默认参数:

def f(x, a = None):
    if a is None:
        a = []
    # ...

查看Python tutorial了解详细说明。

【讨论】:

  • 感谢您的澄清。
【解决方案2】:

我不确定这是否是问题所在,但您必须非常小心地在这样的函数中设置默认列表:

def __init__(self,bind="F",rule=["F"]):
    self.bind = bind
    self.rule = rule

每次调用此函数时,它都会使用相同的列表!因此,每次您在一个 Rule 对象中修改 self.rule 时,都会在您拥有的所有其他对象中修改它。

相反,您应该这样做:

def __init__(self,bind="F",rule=None):
    self.bind = bind
    if not rule:
        self.rule = ['F']
    else:
        self.rule = rule

这样,您每次都创建一个新的列表对象,而不是重复使用同一个。


例如:

class foo:
  def __init__(self, x=[]):
     self.x = x
     self.x.append(3)

d = foo()
e = foo()
f = foo()
print f.x   # prints [3,3,3]

【讨论】:

  • 这增加了我对 Python 的理解。这是一种非常奇怪的编程语言。
  • 是的,它可能是除了所有其他语言之外最奇怪的编程语言。 effbot.org/zone/default-values.htm
【解决方案3】:

我对此不是 100% 确定,但我怀疑问题在于 Python 中的变量是引用而不是值。也就是说,当您设置seq[i] = self.rule 时,seq[i] 指向self.rule 在内存中的存储位置,并且对seq[i] 的值所做的任何更改都会导致对self.rule 的更改,因为它们的值存储在内存中的相同位置。

您可能应该做的是将self.rule 的值深复制到seq[i],而不是简单地分配它,以便它们使用两个不同的内存地址来存储它们的值(有关更多信息,请参阅http://docs.python.org/library/copy.html),这样分配给seq[i] 不会影响self.rule

【讨论】:

  • 我不确定你到底在说什么,你可能完全正确,但现在的说法是恕我直言,相当混乱。 Python 使用称为引用的东西,但它们仅与 C++ 引用和传递引用共享名称。在seq[i] = self.rule之后,seq[i] = ...不会改变self.rule,虽然seq[i].attr = ...会改变对象self.rule指向(即改变为attr可见为self.rule.attr)。
  • 深拷贝解决了这个问题。谢谢!
【解决方案4】:

它会改变,因为rule 是一个列表的引用。然后分配seq[i] 来引用self.rule 指向的对象。因此,当您修改 seq 时,您正在修改 self.rule 引用的同一对象。

>>> a = [1,2,3,4]
>>> b = a
>>> b.append(5)
>>> print a
[1, 2, 3, 4, 5]

我相信您正在寻找的是存储在rule 中的列表的副本。这可以通过几种方式完成。首先,您从当前的可迭代对象构造一个新列表。

seq[i] = list(self.rule) # Makes a new list that is a copy of self.rule

如果self.rule 包含不可变的列表(整数、字符串等),那么这就足够了。然而,如果self.rule 包含可变变量,例如类实例,那么您最终会得到一个新列表,该列表引用self.rule 中引用的所有相同对象。这意味着什么可以在下面的代码中看到:

>>> class A(object):
...     def __init__(self, val):
...        self.x = val

>>> a = [A(i) for i in xrange(10)]
>>> print a[0].x
0
>>> print a[1].x
1
>>> b = list(a)
>>> b.append(A(10))
>>> print b[0].x
0
>>> b[0].x = 99
>>> print b[0].x
99
>>> print a[0].x
99
>>> print (len(a), len(b))
(10, 11)

这里我们有两个不同的列表,但是两个不同的列表都有 10 个共同的引用。 List b 有一个额外的参考,进一步巩固了它的区别。为避免该问题,您可以使用第二种方法,deepcopy

>>> from copy import deepcopy
>>> a = [A(i) for i in xrange(10)]
>>> b = deepcopy(a)
>>> b[0].x = 99
>>> print b[0].x
99
>>> print a[0].x
0

这会复制列表和该列表中的所有对象。

最后,我必须提醒您不要将列表用作rule 关键字参数的默认值。这几乎肯定不会按您期望的方式工作。关键字参数rule定义 处获取列表["F"] 的实例。所有对__init__ 的调用都将获得same 列表实例,因此修改该列表将影响rule 更改时的默认行为。另一个例子见Default value of memberfunction

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-01-01
    • 2014-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-12
    相关资源
    最近更新 更多