函数不会更改可变对象。怎么调试呢?

时间:2016-05-06 19:34:10

标签: python

我遇到过一种情况,我无法在更简单的环境中重现(所以,抱歉没有提供完全可重现的例子)。情况如下。我有一个大类和一个方法(让它调用测试)。此方法将OrderedDict(来自collections包)作为参数并更改它,但返回一些字符串。我知道字典是一个可变对象,所以我不需要返回它(至少在理论上)。然而,奇怪的是,当我从这个方法返回时,我的字典参数保持不变。所以,如果我简化它,代码流看起来像这样:

 from collections import OrderedDict as dord

 # large large class...

 #class method
    def test (self, obj):
        obj['b'] = 2
        print("BEFORE: ")
        print(obj)
        return "Hello world"

if __name__ == "__main__":
    c = TestClass()
    obj = dord()
    obj['a'] = 1          
    c.test(obj)
    print("AFTER: ")
    print(obj)

这就是我在控制台中看到的:

>>> Before:
>>> OrderedDict([('a', '1'), ('b', 2)])
>>> After:
>>> OrderedDict([('a', '1')])

正如我所说,它不是完全可重复的,至少我无法在一个简单的情况下重现它(很抱歉两次)。然而,在我更广泛的背景下(有大班和大方法),我面对这种情况。出于调试目的,我还放了两个打印语句(就像上面一样 - 一个在返回之前,另一个在调用之后)。所以,这一切在我看来都有些副作用。我并没有要求解决方案,而是提供了一些关于如何进行额外调试的建议。

第1屏

enter image description here

第2屏

enter image description here

第3页

enter image description here

因此,正如您从这些屏幕中看到的那样,我首先调用join_aliases_tables,此方法调用一些静态方法SQL_Utils.attrs_tables_set(对attrs_tables参数进行一些修改),在此修改之后我做了我的第一次打印,然后返回,然后是第二次打印。

修改

然而,如果我做世界上最微小的变化 - 返回我的attrs_tables论点 - 那么它就开始工作了。所以,如果我这样做:

print("BEFORE:")
print(attrs_tables)                
return join_str, attrs_tables 

#and

join_str, attrs_tables = self.join_aliases_tables(table_name, attrs, attrs_as, \
                                            attrs_case, join, descs, \
                                            attrs_extra, attrs_tables, \
                                            alias_tables, table_alias, \
                                            tables, alias, where, \
                                            cache_case)                                                
print("AFTER: ")
print(attrs_tables)

然后我在控制台中看到一张漂亮的照片: enter image description here

所以,出于某种疯狂的原因,我必须返回我的可变对象以查看所有更改,如果我不返回它,那么我会得到一张奇怪的图片。

示范

from collections import OrderedDict as dord

class FirstClass(object):
    @staticmethod
    def changeIt(d):
        d['b'] = 'b'

class SecondClass(object):
     def __init__(self):
        pass
     def test(self, d):
        d = dord((k.upper(), v.upper()) for k, v in d.items())
        FirstClass.changeIt(d)

if __name__ == "__main__":

    d = dord({'a':'a'})
    t = SecondClass()
    t.test(d)
    print(d)

1 个答案:

答案 0 :(得分:1)

在演示示例中

from collections import OrderedDict as dord

class FirstClass(object):
    @staticmethod
    def changeIt(d):
        d['b'] = 'b'

class SecondClass(object):
    def __init__(self):
        pass
    def test(self, d):
        d = dord((k.upper(), v.upper()) for k, v in d.items())
        FirstClass.changeIt(d)

if __name__ == "__main__":

    d = dord({'a':'a'})
    t = SecondClass()
    t.test(d)
    print(d)

来自main的d保持不变,因为您不会随时修改它,当您在该函数内调用t.test(d)时,您使用d = dord(...)创建一个新的dict,请记住使用var = value的python,你创建一个具有该值的新变量,即使该变量与其他变量的名称相同,在这种情况下,您只需将引用交换到另一个对象。

在函数test中,您传递对外部d的引用,并将其保存在一个名为d的变量中,然后在此第二个d中指定一个新参考丢失对原始对象的引用的新对象在此过程中保持不变。

要解决此问题,您需要更改该外部对象,您需要使用该对象为此目的提供的方法,即您var.method访问的方法。

上面的例子可以是

from collections import OrderedDict as dord

class FirstClass(object):
    @staticmethod
    def changeIt(d):
        d['b'] = 'b'

class SecondClass(object):
    def __init__(self):
        pass
    def test(self, d):
        #save the temporary result
        temp = dord((k.upper(), v.upper()) for k, v in d.items())
        #empty the old one
        d.clear()
        #update with the new data
        d.update(temp)
        FirstClass.changeIt(d)

if __name__ == "__main__":

    d = dord({'a':'a'})
    t = SecondClass()
    t.test(d)
    print(d)

输出

OrderedDict([('A', 'A'), ('b', 'b')])

但我不建议这样做,而是返回新对象并将其分配给原始变量。

推荐讲座:

http://nedbatchelder.com/text/names.html

https://jeffknupp.com/blog/2012/11/13/is-python-callbyvalue-or-callbyreference-neither/

http://robertheaton.com/2014/02/09/pythons-pass-by-object-reference-as-explained-by-philip-k-dick/