Очень похоже на этот вопрос. Мне было интересно, что более идиоматично и быстрее при использовании Cython: блок try
/except KeyError
или альтернативный решения на основе pop
и/или in
.
Данные испытаний:
from random import shuffle, randint
datal = list(map(float, range(1_000)))
shuffle(datal)
data = {
a * 10 ** (randint(0, 2) * 3): b
for a, b in zip(datal[:-1], datal[1:])
}
data[datal[-1] * 10 ** (randint(0, 2) * 3)] = datal[0]
Три варианта на Cython (уровень языка 3):
- В основном динамическая типизация,
pop
иNone
в качестве возвращаемого значения по умолчанию:
def test_none(dict raw):
_, last_value = raw.popitem()
cdef list out = [last_value]
while len(raw) > 0:
next_value = raw.pop(last_value, None)
if next_value is not None:
out.append(next_value)
last_value = next_value
continue
next_value = raw.pop(last_value * 1_000, None)
if next_value is not None:
last_value = next_value
out.append(next_value)
continue
next_value = raw.pop(last_value * 1_000_000)
out.append(next_value)
last_value = next_value
return out
- С
in
иpop
:
def test_in(dict raw):
cdef double last_value, next_value, _
_, last_value = raw.popitem()
cdef list out = [last_value]
while len(raw) > 0:
if last_value in raw:
next_value = raw.pop(last_value)
elif last_value * 1_000 in raw:
next_value = raw.pop(last_value * 1_000)
else:
next_value = raw.pop(last_value * 1_000_000)
out.append(next_value)
last_value = next_value
return out
- С
pop
иtry/except KeyError
:
def test_te(dict raw):
cdef double last_value, next_value, _
_, last_value = raw.popitem()
cdef list out = [last_value]
while len(raw) > 0:
try:
next_value = raw.pop(last_value)
except KeyError:
try:
next_value = raw.pop(last_value * 1_000)
except KeyError:
next_value = raw.pop(last_value * 1_000_000)
out.append(next_value)
last_value = next_value
return out
С timeit
я получаю следующие результаты:
%timeit test_te(data.copy())
471 µs ± 1.57 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit test_in(data.copy())
219 µs ± 9.04 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit test_none(data.copy())
175 µs ± 401 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
test_none
иtest_in
выглядят нормально. Второй, вероятно, более идиоматичен, но люди могут не согласиться. В любом случае, использование исключения на критическом пути горячего цикла никогда не будет хорошей идеей. Обычно это медленно и не идиоматично, поскольку исключения должны быть редкими, а не обычными (отсюда и название). - person Jérôme Richard   schedule 13.06.2021try
/except
на самом деле самые быстрые (!). Я использую кортежи целых чисел в качестве ключа и объекты, поступающие из класса расширения, в качестве значений - вот в чем различия. Поэтому я не уверен, что на самом деле лучше - конкретно, почему и при каких обстоятельствах. - person s-m-e   schedule 13.06.2021except
, как в моем маленьком тесте (относительно). Имеет смысл. - person s-m-e   schedule 13.06.2021