Python:发生了不必要的变量更改

时间:2011-10-27 14:20:33

标签: python class list

  

可能重复:
  “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', [...]]

为什么呢?我没有为该变量分配新值,但它仍会发生变化。

4 个答案:

答案 0 :(得分:2)

默认参数在定义函数时计算一次,稍后重复使用。

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

答案 1 :(得分: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]

答案 2 :(得分:2)

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

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

答案 3 :(得分:1)

它会更改,因为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个共同的引用。列表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__的所有调用都将获得相同的列表实例,因此修改该列表会影响rule更改时的默认行为。有关其他示例,请参阅Default value of memberfunction