一般情况下很难做到,但对于一些实际情况,你可以这样做:
def call_function_checking_for_modification(f, *args, **kwargs):
myargs = [deepcopy(x) for x in args]
mykwargs = dict((x, deepcopy(kwargs[x])) for x in kwargs)
retval = f(*args, **kwargs)
for arg, myarg in izip(args, myargs):
if arg != myarg:
raise ValueError, 'Argument was modified during function call!'
for kwkey in kwargs:
if kwargs[kwkey] != mykwargs[kwkey]:
raise ValueError, 'Argument was modified during function call!'
return retval
但是,很明显,这存在一些问题。对于琐碎的事情(即所有输入都是简单类型),无论如何这并不是很有用 - 这些可能是不可变的,并且无论如何它们比复杂类型更容易(嗯,相对地)检测。
不过,对于复杂类型,deepcopy 会很昂贵,并且不能保证 == 运算符实际上可以正常工作。 (并且简单的副本还不够好......想象一个列表,其中一个元素更改了值......一个简单的副本将只存储一个引用,因此原始值也会发生变化)。
不过,一般来说,这并不是那么有用,因为如果您已经担心调用此函数的副作用,您可以更智能地防范它们(如果需要,通过存储您自己的副本,审核目标函数,等),如果您担心会导致副作用是您的功能,您将对其进行审核以确保。
不过,像上面这样的东西可以被包裹在一个装饰器中;由于昂贵的部分由全局变量(if _debug == True:,类似的东西)控制,它可能在很多人编辑相同代码的项目中很有用,不过,我猜......
编辑:这仅适用于预期会出现更“严格”形式的“副作用”的环境。在许多编程语言中,您可以更明确地提供副作用 - 例如,在 C++ 中,除非显式指针或引用,否则一切都是按值计算的,即使这样,您也可以将传入引用声明为 const 以便它可以t被修改。在那里,“副作用”可能会在编译时引发错误。 (当然有办法得到一些反正)。
上面强制任何修改的值都在返回值/元组中。如果您在 python 3 中(我还没有),我认为您可以在函数声明本身中指定修饰以指定函数参数的属性,包括是否允许修改它们,并将其包含在上述函数中以允许一些参数明确地是可变的。
请注意,我认为您也可以这样做:
class ImmutableObject(object):
def __init__(self, inobj):
self._inited = False
self._inobj = inobj
self._inited = True
def __repr__(self):
return self._inobj.__repr__()
def __str__(self):
return self._inobj.__str__()
def __getitem__(self, key):
return ImmutableObject(self._inobj.__getitem__(key))
def __iter__(self):
return self.__iter__()
def __setitem__(self, key, value):
raise AttributeError, 'Object is read-only'
def __getattr__(self, key):
x = getattr(self._inobj, key)
if callable(x):
return x
else:
return ImmutableObject(x)
def __setattr__(self, attr, value):
if attr not in ['_inobj', '_inited'] and self._inited == True:
raise AttributeError, 'Object is read-only'
object.__setattr__(self, attr, value)
(可能不是一个完整的实现,没有进行太多测试,但是一个开始)。像这样工作:
a = [1,2,3]
b = [a,3,4,5]
print c
[[1, 2, 3], 3, 4, 5]
c[0][1:] = [7,8]
AttributeError: Object is read-only
如果您不信任下游函数,它可以让您保护特定对象不被修改,同时仍然相对轻量级。尽管如此,仍然需要明确包装对象。尽管对于所有参数,您都可以构建一个装饰器来半自动地执行此操作。确保跳过那些可调用的。