Проверьте, есть ли в dict или try/except, который имеет лучшую производительность в python?

У меня есть несколько словарей, содержащих похожие данные.

Большинство запросов будут разрешены при одном поиске одного словаря.

Итак, лучше ли работать с предварительными проверками на наличие ключа в dict и попробовать в следующем dict в случае исключения ключа исключения?

Или, может быть, что-то вроде

# d1, d2, d3 = bunch of dictionaries

value = d1.get(key, d2.get(key, d3.get(key, 0)))

?

+4
источник поделиться
6 ответов

Зависит от клавиш в словарях.

Если вы с уверенностью прогнозируете, что чаще всего используются ключи, то используйте get.

Если вы с уверенностью прогнозируете, что чаще всего используются ключи, используйте try except.

+4
источник

Кажется, что почти во всех случаях использование get будет быстрее. Вот мой тестовый прогон с использованием try..except и получите

>>> def foo1(n):
    spam = dict(zip(range(-99,100,n),[1]*200))
    s = 0
    for e in range(1,100):
        try:
            s += spam[e]
        except KeyError:
            try:
                s += spam[-e]
            except KeyError:
                s += 0
    return s

>>> def foo2(n):
    spam = dict(zip(range(-99,100,n),[1]*200))
    s = 0
    for e in range(1,100):
        s += spam.get(e, spam.get(-e,0))
    return s


>>> for i in range(1,201,10):
    res1 =  timeit.timeit('foo1({})'.format(i), setup = "from __main__ import foo1", number=1000)
    res2 =  timeit.timeit('foo2({})'.format(i), setup = "from __main__ import foo2", number=1000)
    print "{:^5}{:10.5}{:10.5}{:^10}{:^10}".format(i,res1,res2,foo1(i),foo2(i))


  1    0.075102  0.082862    99        99    
 11     0.25096  0.054272    9         9     
 21      0.2885  0.051398    10        10    
 31     0.26211  0.060171    7         7     
 41     0.26653  0.053595    5         5     
 51      0.2609  0.052511    4         4     
 61      0.2686  0.052792    4         4     
 71     0.26645  0.049901    3         3     
 81     0.26351  0.051275    3         3     
 91     0.26939  0.051192    3         3     
 101      0.264  0.049924    2         2     
 111     0.2648  0.049875    2         2     
 121    0.26644  0.049151    2         2     
 131    0.26417  0.048806    2         2     
 141    0.26418  0.050543    2         2     
 151    0.26585  0.049787    2         2     
 161    0.26663  0.051136    2         2     
 171    0.26549  0.048601    2         2     
 181    0.26425  0.050964    2         2     
 191     0.2648  0.048734    2         2     
>>>
+4
источник

Поскольку вы говорите, что большинство запросов будут разрешены, посмотрев на первый dict, самым быстрым решением было бы сделать что-то вроде:

try:
    item = d1[key]
except KeyError:
    try:
        item = d2[key]
    except KeyError:
        ...

Тем не менее, это, безусловно, не самый удобный вид решений, и я не рекомендую его использовать. Вы можете создать функцию:

def get_from(item,dicts):
    for d in dicts:
        try:
           return d[item]
        except KeyError:
           pass
    else:
        raise KeyError("No item in dicts")

который вы бы назвали следующим:

get_from(key,(d1,d2,d3))

(это упрощенная, немного менее чистая версия уже очень простого рецепта Chained map, предложенного @MartijnPieters в комментариях к исходному вопросу - я бы рекомендовал использовать это над этим кодом, размещенным здесь. чтобы продемонстрировать концепцию более упрощенным способом.)

Наконец, возможно, гибридное решение будет работать лучше всего на практике. Коэффициент первого try из цикла - это немного уродливый, но он в большинстве случаев избегает накладных расходов loop. Только если первый try вызывает a KeyError, вы вводите решение типа петли, которое я предложил выше на остальных dicts. например:.

try:
   item = d1[key]
except KeyError:
   item = get_from(key,(d2,d3))

снова, только сделайте это, если вы можете надежно продемонстрировать (подумайте timeit), что он делает заметную разницу


Важно знать, что в python try дешево, но except стоит приличного количества времени. Если ваш код будет успешным, используйте try - except. Если это не оправдано, часто лучше использовать try-except в любом случае, но в этом случае вы должны оценить, является ли производительность действительно проблемой, и только если вы можете продемонстрировать, что это проблема, вы должны прибегнуть к "поиску прежде чем прыгать".

Последнее замечание. Если словари относительно статичны, возможно, стоит их комбинировать в 1 dict:

d1.update(d2)
d1.update(d3)

Теперь вы можете просто использовать d1 - он имеет всю информацию из d2 и d3. (конечно, порядок обновлений имеет значение, если у dicts есть ключи одинаковые, но имеют разные значения).

+1
источник

try...except обычно занимает больше времени, чем при использовании get, но это зависит от нескольких вещей...

Попробуйте использовать модуль timeit для проверки производительности в вашей конкретной ситуации следующим образом:

def do_stuff():
    blah

timeit.timeit('testfunc()', 'from __main__ import do_stuff as testfunc')
+1
источник

Вы могли бы также сделать

sentinel = object()
values = (d.get(key, sentinel) for d in (d1, d2, d3))
value = next(v for v in values if v is not sentinel)

Если ни один из ключей не содержит ключ, это вызывает StopIteration, а не KeyError.

0
источник

Разница между проверкой условного

if 'key' in a_dict или аналогичным образом,  if a_dct.get('key') == None

и обрабатывать KeyError, когда not 'key' in a_dict обычно считается тривиальным и, вероятно, зависит от реализации используемого вами питона.

Использование условной формы, несомненно, более питонов и, как правило, считается более выразительным, чем устранение исключения, часто приводящее к созданию более чистого кода. Однако, если ваш словарь может содержать произвольные данные, и вы не можете знать, что значение None или какое-либо другое магическое значение указывает на то, что ваш ключ не найден, использование условной формы потребует двух поисковых запросов, поскольку вы сначала проверяете, ключ находится в словаре, а затем извлекает значение. То есть:.

if 'key': in a_dict:
   val = a_dcit['key']

Учитывая описанную ситуацию, код, который вы предоставили, является самым медленным возможным вариантом, так как key будет отображаться в каждом из словарей. Более быстрый вариант - угадать словарь, в котором он будет, и последовательно искать через другие словари:

my_val = d1.get(key,None)

if my_val == None:
    my_val = d2.get(key,None)
    if my_val == None:
        my_val = d3.get(key,None)
        if my_val == None:
            return False #handle not found in any case

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

dict_list = [{},{},{}] #pretend you have three dicts in a list

for d in dict_list:
   val = d.get('key',None)
   if val == None:
      break
#val is now either None, or found.
0
источник

Посмотрите другие вопросы по меткам или Задайте вопрос