python:无意中修改传递给函数的参数
有几次我不小心修改了一个函数的输入。由于Python没有常量引用,我想知道哪些编码技术可以帮助我避免经常犯这种错误 例如:python:无意中修改传递给函数的参数,python,coding-style,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 element
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通过引用传递参数的方法,这大概是有原因的
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
第一条一般规则:不要修改容器:创建新容器 所以,不要修改传入的字典,而是创建一个包含键子集的新字典
self.fields = dict( key, value for key, value in fields.items()
if accept_key(key, data) )
这样的方法通常比遍历和删除坏元素稍微有效一些。一般来说,避免修改对象而创建新对象通常更容易
第二条一般规则:不要在容器通过后修改它们
一般来说,您不能假设您向其传递数据的容器已经制作了自己的副本。因此,不要试图修改您提供给它们的容器。在移交数据之前,应进行任何修改。一旦你把容器交给别人,你就不再是它的唯一主人了
第三条一般规则:不要修改您没有创建的容器
如果您通过某种容器,您不知道还有谁在使用该容器。所以不要修改它。使用未修改的版本或调用rule1,创建具有所需更改的新容器
第四条一般规则:(从Ethan Furman那里偷来)
有些函数应该修改列表。那是他们的工作。如果是这种情况,请在函数名(例如列表方法append和extend)中显示这一点
将所有内容放在一起:
只有当容器是唯一可以访问该容器的代码时,一段代码才应该修改该容器 复制“以防万一”的参数是一个坏主意:你最终付出的代价是糟糕的性能;或者,您必须跟踪参数的大小 更好地理解对象和名称以及Python如何处理它们。这是一个良好的开端 重要的一点是
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_list
和alist
是两个不同的列表对象,恰好包含相同的对象——因此,如果您只是搞乱了列表对象(插入、删除、重新排列),那么您就没事了,buf如果您想更深入地修改列表中的对象,则需要执行deepcopy()
。但是,由您来跟踪这些事情并适当地编写代码。在Python中,有一种最佳实践,称为单元测试
这里的要点是,动态语言允许快速开发,即使是完整的单元测试;单元测试是比静态类型更为严格的安全网。如文所述: 静态类型的一般论点是,它捕获的bug是 否则很难找到。但我发现,在 在SelfTestingCode中,发现了大多数静态类型的bug 同样容易通过测试
除了总是使用不可变类型之外?如果我需要一本字典,我怀疑使用一个冻结的dict(我必须自己实现)是否值得?谢谢。虽然在传递对象之后不修改它们,但这有点太极端了,不是吗?由于我必须允许任何函数修改它们,这意味着在我将对象
x
传递给一个函数后,我不能将它传递给任何其他函数?@max,添加了第三条一般规则:函数不应该修改x
。一般的想法是,只有当只有一段代码在使用容器时,才可以真正安全地修改它。@max您可能对函数式编程感兴趣-它们根本不需要修改现有对象(大多数情况下)就可以完成。这种编程风格有一些非常有用的优点。第四条一般规则:如果您的函数确实修改了传入的容器,请在函数名中显示它(例如列表方法附加和扩展)。这听起来像是唯一现实的结果,因为我非常喜欢单元测试,我看不出它在这里有多大帮助。也许吧。我怀疑单元测试可能会漏掉这个bug,因为它只在将同一个容器传递到多个位置时发生。您必须进行集成测试。对于如何避免动态类型语言中的bug这个问题,这是一个很好的答案。这在这里似乎不合适,因为它没有引用OP询问的bug类型。OP知道他经常犯这个错误,因此在函数完成后让单元测试检查容器可能会发现bug——但是,如果测试中没有发生更改,它将