python:无意中修改传递给函数的参数

时间:2011-09-11 17:26:46

标签: python coding-style

有几次我意外地将输入修改为一个函数。由于Python没有常量引用,我想知道什么编码技术可以帮助我避免经常犯这个错误?

示例:

class Table:
  def __init__(self, fields, raw_data):
    # fields is a dictionary with field names as keys, and their types as value 
    # sometimes, we want to delete some of the elements 
    for field_name, data_type in fields.items():
      if some_condition(field_name, raw_data):
        del fields[field_name]
    # ...


# in another module

# fields is already initialized here to some dictionary
table1 = Table(fields, raw_data1) # fields is corrupted by Table's __init__
table2 = Table(fields, raw_data2)

当然,修复方法是在更改之前复制参数:

  def __init__(self, fields, raw_data):
    fields = copy.copy(fields)
    # but copy.copy is safer and more generally applicable than .copy 
    # ...

但这很容易忘记。

我一半想在每个函数的开头复制每个参数,除非参数可能引用一个大数据集,复制起来可能很昂贵,或者除非要修改参数。这几乎可以消除这个问题,但是在每个函数开始时会导致大量无用的代码。此外,它基本上会覆盖Python通过引用传递参数的方法,这可能是出于某种原因。

5 个答案:

答案 0 :(得分:12)

第一条一般规则: 不修改容器:创建新容器。

因此,请勿修改传入的字典,使用键的子集创建新的字典。

self.fields = dict( key, value for key, value in fields.items()
                     if accept_key(key, data) )

此类方法通常稍微更有效,然后通过并删除不良元素。更一般地说,它通常更容易避免修改对象而是创建新对象。

第二条一般规则: 在传递容器后不要修改容器。

您通常不能假设您传递数据的容器已经创建了自己的副本。因此,请勿尝试修改您提供的容器。在处理数据之前应该进行任何修改。一旦你将容器传递给其他人,你就不再是它的唯一主人了。

第三条一般规则: 不要修改您未创建的容器。

如果你通过某种容器,你不知道还有谁在使用容器。所以不要修改它。使用未修改的版本或调用rule1,创建具有所需更改的新容器。

第四条一般规则:(从Ethan Furman被盗)

某些功能应该修改列表。那是他们的工作。如果是这种情况在函数名称中显示(例如列表方法追加和扩展)。

全部放在一起:

一段代码只应在容器访问该容器时才能修改容器。

答案 1 :(得分:4)

复制参数“只是在案件中”是一个坏主意:你最终在糟糕的表现中付出代价;或者你必须跟踪你的论点的大小。

更好地理解对象和名称以及Python如何处理它们。一个良好的开端是this article

重点是

def modi_list(alist):
    alist.append(4)

some_list = [1, 2, 3]
modi_list(some_list)
print(some_list)

完全相同
some_list = [1, 2, 3]
same_list = some_list
same_list.append(4)
print(some_list)

因为在函数调用中没有发生参数复制,所以没有创建指针......正在发生的事情是Python说alist = some_list然后在函数modi_list()中执行代码。换句话说,Python是绑定(或指定)另一个名称到同一个对象

最后,如果你有一个将要修改其参数的函数,并且你不希望这些更改在函数外部可见,你通常可以只做一个浅拷贝:

def dont_modi_list(alist):
    alist = alist[:]  # make a shallow copy
    alist.append(4)

现在some_listalist是碰巧包含相同对象的两个不同的列表对象 - 所以如果你只是搞乱列表对象(插入,删除,重新排列)那么你很好,如果您要更深入并导致对列表中对象的修改,那么您需要执行deepcopy()。但是你应该适当地跟踪这些事情和代码。

答案 2 :(得分:1)

您可以按如下方式使用元类:

import copy, new
class MakeACopyOfConstructorArguments(type):

    def __new__(cls, name, bases, dct):
        rv = type.__new__(cls, name, bases, dct)

        old_init = dct.get("__init__")
        if old_init is not None:
            cls.__old_init = old_init
            def new_init(self, *a, **kw):
                a = copy.deepcopy(a)
                kw = copy.deepcopy(kw)
                cls.__old_init(self, *a, **kw)

        rv.__init__ = new.instancemethod(new_init, rv, cls)
        return rv

class Test(object):
    __metaclass__ = MakeACopyOfConstructorArguments

    def __init__(self, li):
        li[0]=3
        print li


li = range(3)
print li
t = Test(li)
print li

答案 3 :(得分:1)

在Python中有一种最佳实践,称为单元测试。

这里的要点是,即使有完整的单元测试,动态语言也可以实现快速开发。和静态打字相比,单元测试是一个更加严格的安全网 写作Martin Fowler

  

静态类型的一般参数是它捕获的bug   否则很难找到。但我发现在那里   SelfTestingCode,找到了静态类型可能存在的大多数错误   同样容易通过测试。

答案 4 :(得分:0)

正如@delnan所说,最简单的解决方案是始终传递不可变量。您还可以将变量包装在自定义常量对象中。

Python: Any way to declare constant parameters?