有几次我意外地将输入修改为一个函数。由于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通过引用传递参数的方法,这可能是出于某种原因。
答案 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_list
和alist
是碰巧包含相同对象的两个不同的列表对象 - 所以如果你只是搞乱列表对象(插入,删除,重新排列)那么你很好,如果您要更深入并导致对列表中对象的修改,那么您需要执行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所说,最简单的解决方案是始终传递不可变量。您还可以将变量包装在自定义常量对象中。