检查Python函数是否不修改参数?

时间:2017-01-03 13:20:38

标签: python

你知道如何在Python中,如果v是一个列表或一个字典,那么编写修改f的函数(而不仅仅是返回新值)是很常见的。我想知道是否可以编写一个识别这些功能的检查器。

为简单起见,假设您有一个函数a,它只接受一个参数 - v并在有限时间内返回(返回值实际上是无关紧要的)。还假设对于任何输入值f(v)f总是做同样的事情(即a内部的逻辑不依赖于任何上下文或环境值 - 它是对{的纯粹计算{1}})。

是否可以编写一个函数m,以便当m(f, v)实际更改True的原始值时,f(v)会返回v

4 个答案:

答案 0 :(得分:4)

没有;这相当于Halting problem。如果这样的m确实存在,那么我可以写:

def f(a):
    if m(f, a):
        return a
    else:
        # Modify `a` somehow
        return a

我们之间存在矛盾。

答案 1 :(得分:2)

如果您想检查该行为,可以使用原始值的deepcopy编写简单的黑盒测试,例如:

def m(f, a):
    original = copy.deepcopy(a)
    f(a)
    return original != a

def f(a):
    a.append('a')

def k(a):
    b = a

z = ['b']
m(f, z) # True
z = ['b']
m(k, z) # False

当然,如果参数是list dict,你必须深入复制并比较内部对象,但它是相同的逻辑

答案 2 :(得分:0)

非常简陋第一次出现在这只适用于iterables(并且没有:我不声称这解决了暂停问题或在一般情况下工作......):

from collections.abc import Sequence

def changes(lst):
    lst.append(0)

def no_changes(lst):
    return

def tries_to_change(f, v):
    if isinstance(v, Sequence):
        v_immutable = tuple(v)
        try:
            f(v_immutable)
            return False
        except AttributeError:
            return True

print(tries_to_change(f=changes, v=[1, 2, 3]))  # True
print(tries_to_change(f=no_changes, v=[1, 2, 3]))  # False

这个想法是将输入转换为相同数据结构的不可变版本,看看会发生什么。非常粗糙!

并且在评论中提到 jbasko :这只会阻止元素的设置和删除;元素本身的修改(例如,如果参数是列表的列表;您仍然可以更改'内部'列表)将不会被检测到。

由于 PM 2Ring 的评论,

小更新:如果列表包含可变的东西,这种方法不起作用(函数返回None [这与停止问题一致答案...])。

def tries_to_change(f, v):
    if isinstance(v, Sequence):
        # check if the sequence contains immutable elements only:
        try:
            set(v)
        except TypeError:
            # no idea what could happen to the elements in the list...
            return None

        v_immutable = tuple(v)
        try:
            f(v_immutable)
            return False
        except AttributeError:
            return True

答案 3 :(得分:0)

如果您知道 总是更改v从不更改v,那么您可以做这样的事情:

class Checker(dict):
    def __init__(self):
        super().__init__()
        self.changed = False

    def __setitem__(self, index, value):
        super().__setitem__(index, value)
        self.changed = True

    # implementing the rest of the mutating methods, e.g. `update`
    # is left as an exercise for the reader

def m(f, v):
   '''return True if f modifies v. Otherwise, return False'''
   c = Checker()
   c.update(v)
   f(c)
   return c.changed

可能能够使用一些代码路径执行检查来查看是否所有路径都已执行,和/或做一些奇怪的AST黑客攻击以删除任何类型的条件......但是你&# 39; d还必须确保没有像这样的任何愚蠢的诡计:

def f(v):
    '''Do terrible things in terrible ways.'''

    q = v
    if q.update({1:1}):
        pass  # because it will never hit here, but it *will* modify `v`
    qux = [1,2]
    qux.append({'derp': v})
    qux[2]['derp'][42] = '...herring. A red one!'