1

我写了一些代码,假设对超集的检查将是内存友好的并导致更少的碎片,因为它返回一个布尔值(a_list总是不大于 2 个非常小的字符串元素,与fooand的顺序相同bar)。例如

OK_SET = set('foo', 'bar')

def are_args_ok(a_list):
    if not OK_SET.issuperset(a_list): # expected to run a lot
        raise ValueError('bad value in a_list') # virtually never

而且我认为上面比下面更可取,如果只是为了可读性,而且因为我认为最好不要创建很多不必要的列表,并且我希望它不会创建任何其他对象,因为它只返回一个布尔值。

def are_args_ok(a_list):
    if [i for i in a_list if i not in ['foo', 'bar']]: # expected to run a lot
        raise ValueError('bad value in a_list') # virtually never

但是,我不清楚 Python 的所有内部工作原理。因此,我一直在阅读CPython 源代码(下面的摘录),如果它还不是一个集合,则似乎检查超集会创建另一个的集合对象:

static PyObject *
set_issuperset(PySetObject *so, PyObject *other)
{
    PyObject *tmp, *result;

    if (!PyAnySet_Check(other)) {
        tmp = make_new_set(&PySet_Type, other);
        if (tmp == NULL)
            return NULL;
        result = set_issuperset(so, tmp);
        Py_DECREF(tmp);
        return result;
    }
    return set_issubset((PySetObject *)other, (PyObject *)so);
}

所以看起来我在给定一个列表作为另一个列表时创建了一个新集合,所以我的假设是错误的,即使它更具可读性。我认为第二个代码实际上可能更快,至少在我使用 Python 2.6 进行测试时是这样。所以我的问题是,就内存性能和碎片而言,第一个代码比第二个代码更可取吗?

有没有我还没有考虑过的严格主导的方法?


编辑:这回答了有关性能的相关问题:

must= '''MUST=set(['a','b'])

def validate(vals):
    if not MUST.issuperset(vals):
        raise Exception'''

mustdiff= '''MUST=set(['a','b'])

def validate(vals):
    if set(vals) - MUST:
        raise Exception'''

must2= '''def validate(vals):
    if not set(['a','b']).issuperset(vals):
        raise Exception'''

old_list = '''def validate(vals):
    if [i for i in vals if i not in ['a','b']]:
        raise Exception
'''

old_tup = '''def validate(vals):
    if [i for i in vals if i not in ('a','b')]:
        raise Exception
'''
test = "validate(['a']); validate(['a', 'b'])"

def main():
    print timeit.repeat(test, setup=must)
    print timeit.repeat(test, setup=mustdiff)
    print timeit.repeat(test, setup=must2)
    print timeit.repeat(test, setup=old_list)
    print timeit.repeat(test, setup=old_tup)

输出:

[0.90473995592992651, 0.90407950738062937, 0.90170756738780256]
[1.0068785656071668, 1.0049370642036592, 1.0076947689335611]
[1.4705243140447237, 1.4697376920521492, 1.4727534788248704]
[0.74187539617878429, 0.74010685502116758, 0.74236680853618964]
[0.74886594826284636, 0.74639892541290465, 0.74475293549448907]
4

2 回答 2

3

我认为第二个代码实际上可能更快,至少在我使用 Python 2.6 进行测试时是这样。

如果是这样的话,我会感到震惊——有问题的列表的大小是多少?我的猜测是,将列表转换为集合可能会产生一些恒定的开销,这会抵消使用集合操作带来的任何性能优势。

集合操作为您提供了这种操作的渐近最优性能。我希望该issuperset方法能够为您提供最佳性能,然后可能是:

if not set(a_list) - OK_SET:...

O(len(a_list))性能。请注意,使用全局变量OK_SET也会严重影响您的性能。

话虽这么说:除非您正在测试包含数千个元素的集合,否则差异可能可以忽略不计。过早的优化是万恶之源。如果您的生产代码实际上只测试两个元素,我怀疑您会发现很多不同。

于 2014-06-03T20:09:30.470 回答
1

对于这么少的项目,这似乎比我尝试的其他几件事要快一些:

    .
    .
    .
kiss = '''MUST=['a','b']

def validate(vals):
    for i in vals:
        if i not in MUST:
            raise Exception
'''

test = "validate(['a']); validate(['a', 'b'])"

def main():
    print '    must:', min(timeit.repeat(test, setup=must))
    print 'mustdiff:', min(timeit.repeat(test, setup=mustdiff))
    print '   must2:', min(timeit.repeat(test, setup=must2))
    print 'old_list:', min(timeit.repeat(test, setup=old_list))
    print ' old_tup:', min(timeit.repeat(test, setup=old_tup))
    print '    kiss:', min(timeit.repeat(test, setup=kiss))

if __name__ == '__main__':
    main()
于 2014-06-03T21:57:05.703 回答