Методы вызова Python, которые возвращают логические значения, такие как `issuperset`, удобны для памяти?

Я написал некоторый код, предполагая, что проверка надмножества будет дружественной к памяти и приведет к меньшей фрагментации, поскольку она возвращает логическое значение (a_list is всегда не больше, чем 2 элемента очень маленьких строк в том же порядке как foo и 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]

person Aaron Hall    schedule 03.06.2014    source источник
comment
Временный набор умрет до того, как issuperset вернется. Если a_list всегда мало, вполне может быть, что он всегда выделяет один и тот же слот для набора. Не значит, что так будет быстрее, но точно удобнее.   -  person    schedule 04.06.2014
comment
a_list всегда мал. Обычно не более 2 элементов.   -  person Aaron Hall    schedule 04.06.2014
comment
Да, и кстати, если вы можете написать ['foo', 'bar'] для OK_SET, вам следует попробовать ('foo', 'bar'), что позволит избежать создания списка для каждого отдельного теста in (кортеж создается один раз во время компиляции и загружается при необходимости).   -  person    schedule 04.06.2014
comment
Можете ли вы дать ссылку на источник об этом? Я еще не копался в компиляции функций.   -  person Aaron Hall    schedule 04.06.2014
comment
Реализация, вероятно, разбросана по многим отдельным файлам (эта оптимизация касается компиляции байт-кода, формата pyc и цикла интерпретатора), но вы можете наблюдать за ней, используя dis (обратите внимание на код операции LOAD_CONST).   -  person    schedule 04.06.2014
comment
Обратите внимание, что в течение 1 000 000 циклов разница составляет не более 700 мс. Это важно для вашей программы?   -  person Patrick Collins    schedule 04.06.2014


Ответы (2)


Для такого небольшого количества элементов это кажется немного быстрее, чем несколько других вещей, которые я пробовал:

    .
    .
    .
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()
person martineau    schedule 03.06.2014
comment
Принятие, потому что это строго доминирует (превосходит по всем параметрам) другие решения. Хотя немного быстрее с MUST в виде набора вместо списка. - person Aaron Hall; 04.06.2014
comment
Я не пробовал это с MUST как set, потому что это, казалось, замедляло другие вещи, которые я пробовал. Кстати, строго говоря, validate() не возвращает логическое значение — оно либо возвращает None, либо вызывает исключение и поэтому никогда не возвращается. - person martineau; 04.06.2014

Я думаю, что второй код на самом деле может быть быстрее, по крайней мере, когда я тестирую Python 2.6.

Я был бы шокирован, если бы это было так — каковы размеры рассматриваемых списков? Я предполагаю, что, возможно, преобразование списка в набор имеет некоторые постоянные накладные расходы, которые сводят на нет любой выигрыш в производительности от использования операций над наборами.

Операции над множествами обеспечивают асимптотически оптимальную производительность для такого рода операций. Я ожидаю, что метод issuperset даст вам наилучшую производительность, за которым, возможно, следуют:

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

с O(len(a_list)) производительностью. Обратите внимание, что использование глобальной переменной OK_SET также значительно ухудшит вашу производительность.

При этом: если вы не тестируете наборы, содержащие тысячи элементов, разница, вероятно, незначительна. Преждевременная оптимизация — корень всех зол. Если ваш производственный код на самом деле тестирует только два элемента, я сомневаюсь, что вы найдете большую разницу.

person Patrick Collins    schedule 03.06.2014
comment
список очень маленький, см. редактирование рядом со вступительным абзацем. Я использую Python 2.6, и глобальная переменная строго доминирует в производительности по сравнению с ее созданием при каждом вызове. +1 за помощь, хотя. - person Aaron Hall; 04.06.2014
comment
Если причиной является преобразование в set, if not set(a_list) - OK_SET не улучшит ситуацию (фактически будет создано два набора, один такой же, как тот, что был встроен в issuperset, а другой — в результате -). - person ; 04.06.2014
comment
@AaronHall, ты проверял это? Я не верю, что использование локальной переменной приведет к ее повторному созданию при каждом вызове. В противном случае используйте его как значение по умолчанию для параметра функции — это, безусловно, оценивается ровно один раз. Но есть большие накладные расходы на глобальные переменные - person Patrick Collins; 04.06.2014
comment
@delnan Да, я не утверждаю, что эта версия позволяет избежать накладных расходов - я сказал, что она будет хуже, чем issuperset, но предоставил ей ссылку на вики Python, потому что ее производительность хорошо задокументирована и асимптотически оптимальна. Однако в случае двухэлементного списка я действительно сомневаюсь, что какая-либо микрооптимизация будет иметь существенное значение. - person Patrick Collins; 04.06.2014