繁体   English   中英

Python“奇怪”的输出

[英]Python “strange” output

class Foo(object):
    def __init__(self,x):
        self.x = x
        self.is_bar = False
    def __repr__(self): return str(self.x)

class Bar(object):
    def __init__(self,l = []):
        self.l = l
    def add(self,o):
        self.l += [o]
    def __repr__(self): return str(self.l)

def foo_plus_foo(f1,f2):
    t = Bar()
    if not (f1.is_bar and f2.is_bar):
        f1.is_bar = True
        f2.is_bar = True
        t.add(f1)
        t.add(f2)
        print 'HERE'
    return t

if __name__ == '__main__':
    li = [Foo(1), Foo(2)]
    print foo_plus_foo(li[0],li[1])
    print foo_plus_foo(li[0],li[1])

意想不到的输出:

HERE
[1, 2]
[1, 2]

预期产量:

HERE
[1, 2]
[]

怎么了? 我做错了什么? 为什么python使用旧值? 我该怎么做才能避免这种情况?

谢谢!

决不。 做。 这个。

def __init__(self,l = []):

决不。

一个list对象被重用。 并且它是Mutable,因此每次重用时,都会更新在方法定义中创建的唯一[]

总是。 做。 这个。

def __init__( self, l= None ):
    if l is None: l = []

这创建了一个全新的,独特的列表实例。

您将l定义为默认值为[] 这是一个经典的Python陷阱

class Bar(object):
    def __init__(self,l = []):

定义时而非运行时评估默认值。 它只评估一次。

所以t=Bar()每次都将tl设置为相同的列表。

要解决此问题,请将Bar更改为

class Bar(object):
    def __init__(self,l = None):
        if l is None:
            l=[]
        self.l = l
    def add(self,o):
        self.l += [o]
    def __repr__(self): return str(self.l)

罪魁祸首是Bar类定义中定义的l = [] 此类列表在类定义期间实例化一次,并用作默认值。 超级危险!! 我和其他许多人被这个人烧伤了,相信我的伤痕很深。

有问题的使用可变。

class Bar(object):
    def __init__(self,l = []):
        self.l = l
    def add(self,o):
        self.l += [o]
    def __repr__(self): return str(self.l)

尝试使用不可变的:

class Bar(object):
    def __init__(self,l = None):
        if l is None:
            self.l = []
        else:
            self.l = l
    def add(self,o):
        self.l += [o]
    def __repr__(self): return str(self.l)

其他人已经解释了这个问题,并建议使用l=None和一个明确的测试。 我想提出一个不同的方法:

class Bar(object):
    def __init__(self, l=[]):
        self.l = list(l)
        # and so on...

这默认每次都保证一个新的空白列表,并且还确保如果调用者传入他们自己的列表,您将获得该列表的副本,而不是对调用者列表的引用。 作为一个额外的好处,调用者可以传入列表构造函数可以使用的任何内容,例如元组或字符串,并且您的代码不必担心这一点; 它只能处理一个列表。

如果您只是保存对调用者给出的列表的引用,并稍后更改名为self.l的列表,则可能无意中更改了传入的列表(因为您的类和调用者现在都引用了同一个对象)。 同样,如果他们在调用构造函数后更改了列表,那么您的“副本”也将被更改(因为它实际上不是副本)。 当然,这可能是你想要的行为,但可能不是这种情况。

虽然如果你从不操纵列表(即添加,替换或删除项目),但只是引用它或批量替换它,复制它通常是浪费时间和内存。

使用list()构造函数创建的副本很浅。 也就是说,列表本身是一个新对象,但如果原始列表包含对可变对象的引用(例如其他列表或字典,或大多数其他类的实例),则更改这些对象时会出现同样的问题,因为它们仍然在列表之间共享。 这个问题的频率低于您的想象,但如果是这样,您可以使用copy模块中的deepcopy()函数执行深层复制。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM