Задать вопрос
solotony
@solotony
покоряю пик Балмера

Что предпочтительнее в python — проверять значение в словаре или обработать исключение исключение?

Какой из вариантов для системы кеширования предпочтительнее?

D = dict()

def f1(x):
    if x not in D:
        D[x] = long_calculation()
    return D[x]

def f2(x):
    try:
        return D[x]
    except:
        D[x] = long_calculation()
    return D[x]
  • Вопрос задан
  • 167 просмотров
Подписаться 1 Простой 1 комментарий
Решения вопроса 1
sergey-gornostaev
@sergey-gornostaev Куратор тега Python
Седой и строгий
Easier to ask for forgiveness than permission. This common Python coding style assumes the existence of valid keys or attributes and catches exceptions if the assumption proves false. This clean and fast style is characterized by the presence of many try and except statements. The technique contrasts with the LBYL style common to many other languages such as C.

https://docs.python.org/3/glossary.html
Ответ написан
Пригласить эксперта
Ответы на вопрос 3
trapwalker
@trapwalker Куратор тега Python
Программист, энтузиаст
Вы можете сами произвести замеры, но ставлю на то, что быстрее окажется проверка по словарю, поскольку она делается за ~O(1) благодаря хешированию.
Однако я бы предложил чуть более эффективню схему:
D = dict()
NOTFOUND = object()

def f1(x):
    result = D.get(x, NOTFOUND)
    if result is NOTFOUND:
        result = D[x] = long_calculation()
    return result


Не поленитесь (как я), сделайте замеры. Всем тут будет интересно.
А ещё в питоне как-то не принято экономить на спичках в ущерб чтаемости и прозрачности кода.
Но если всё как следует "посахарить"... Лучше всего такое кэширование смотрится в виде декоратора.

UPD:
Забавно. Я ошибся и с исключением действительно выходит быстрее. На это указал уже автор вопроса, а я на всякий случай пересчитал, причем по отдельности для прогрева, для повторного взятия и для неполучения из прогретого хеша.

import time
from math import tan, atan
import timeit


NOTFOUND = object()


def long_calculation(x):
    return atan(tan(x) / 2)


def f1(x):
    if x not in D:
        D[x] = long_calculation(x)
    return D[x]


def f2(x):
    try:
        return D[x]
    except:
        D[x] = long_calculation(x)
    return D[x]


def f3(x):
    result = D.get(x, NOTFOUND)
    if result is NOTFOUND:
        result = D[x] = long_calculation(x)
    return result


FUNCS = (
    (f1, 'get triple'),
    (f2, 'except'),
    (f3, 'get once'),
)


def work(f, gap=0.1, count=1000):
    for x in range(0, count):
        f(x + gap)


D = {}
number = 10000

for func, descr in FUNCS:
    print(f'{func.__name__} ({descr}):')
    print(f'  Cache empty:', timeit.timeit(f"work({func.__name__})", setup=f'D=dict()', globals=globals(), number=number))
    print(f'  Total reuse:', timeit.timeit(f"work({func.__name__})", setup=f'D=dict(); work({func.__name__})', globals=globals(), number=number))
    print(f'  Total miss :', timeit.timeit(f"work({func.__name__})", setup=f'D=dict(); work({func.__name__}, gap=0.2)', globals=globals(), number=number))

И вот результат:
f1 (get triple):
  Cache empty: 2.8940897800493985
  Total reuse: 1.7486431139986962
  Total miss : 1.6964515489526093
f2 (except):
  Cache empty: 1.2670072519686073
  Total reuse: 1.2622331579914317
  Total miss : 1.2547212480567396
f3 (get once):
  Cache empty: 1.6983374420087785
  Total reuse: 1.6465996010228992
  Total miss : 1.6999219709541649
Ответ написан
shurshur
@shurshur
Сисадмин, просто сисадмин...
Второй вариант плох уже тем, что ловятся любые исключения, например, если в момент нахождения в этом месте нажать Ctrl-C, то будет пойман KeyboardInterrupt, который приведёт к блоку except. Если и ловить исключение, то явно указывать какое именно.

Кроме того, если в блоке except возникнет exception, то трейс будет ужасный: при обработке исключения возникло исключение... и если таким подходом злоупотреблять, то трейсы могут становиться многоэтажными.

Исключения лучше использовать по их прямому назначению: для отлавливания исключительных ситуаций.
Ответ написан
@o5a
Есть setdefault
D = dict()

def f1(x):
    return D.setdefault(x, long_calculation())
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы