#python #performance #exception-handling #typechecking
#python #Производительность #исключение #проверка типов
Вопрос:
Я работаю с Python, и всякий раз, когда мне приходилось проверять ввод функции, я предполагал, что ввод сработал, а затем обнаруживал ошибки.
В моем случае у меня был универсальный Vector()
класс, который я использовал для нескольких разных задач, одна из которых — сложение. Он функционировал и как Color()
класс, и как Vector()
, поэтому, когда я добавляю скаляр к Color()
, он должен добавлять эту константу к каждому отдельному компоненту. Vector()
и Vector()
дополнение требуется дополнение по компонентам.
Этот код используется для raytracer, поэтому любое повышение скорости является отличным.
Вот упрощенная версия моего Vector()
класса:
class Vector:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def __add__(self, other):
try:
return Vector(self.x other.x, self.y other.y, self.z other.z)
except AttributeError:
return Vector(self.x other, self.y other, self.z other)
В настоящее время я использую try...except
метод. Кто-нибудь знает более быстрый метод?
РЕДАКТИРОВАТЬ: Благодаря ответам я попробовал и протестировал следующее решение, которое специально проверяет имя класса перед добавлением Vector()
объектов:
class Vector:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def __add__(self, other):
if type(self) == type(other):
return Vector(self.x other.x, self.y other.y, self.z other.z)
else:
return Vector(self.x other, self.y other, self.z other)
Я провел тест скорости с использованием этих двух блоков кода timeit
, и результаты были довольно значительными:
1.0528049469 usec/pass for Try...Except
0.732456922531 usec/pass for If...Else
Ratio (first / second): 1.43736090753
Я не тестировал этот Vector()
класс не проверка входных бы то ни было (т. е. перемещение проверить класса и в фактическом коде), но я предполагаю, что это даже быстрее, чем if...else
метод.
Позднее обновление: Оглядываясь назад на этот код, это не оптимальное решение.
ООП делает это еще быстрее:
class Vector:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def __add__(self, other):
return Vector(self.x other.x, self.y other.y, self.z other.z)
class Color(Vector):
def __add__(self, other):
if type(self) == type(other):
return Color(self.x other.x, self.y other.y, self.z other.z)
else:
return Color(self.x other, self.y other, self.z other)
Комментарии:
1. Пожалуйста, используйте
timeit
и включите реальные результаты синхронизации ваших двух альтернатив.2. Вы знаете, что два примера выполняют разные функции? Неясно, что вы измеряете, поскольку они несопоставимы.
3. Я опубликую свой код. Не думайте, что я вручную вводил
'a'
1 000 000 раз в окно терминала 😉4. Ваша
if
проверка не будет работать для отрицательных чисел, таких как ‘-1’5. @Blender: Пожалуйста, сосредоточьтесь на этом вопросе. Ваш if ‘a’.isdigit() совершенно неуместен и сбивает с толку. Пожалуйста, удалите это. Если ваша «настоящая» проблема заключается в вашем
VectorCheck
классе, тогда просто покажите это. Кроме того, не возитесь с причудливыми «линейными выводами» и другой разметкой. ТАКИМ образом, поддерживается полный список изменений. Пожалуйста, просто сосредоточьтесь на одной вещи. Упакуйте свой реальный вопрос во что-то, чему другие могут научиться.
Ответ №1:
Я поддержал ответ Мэтта Джойнера, но хотел бы включить некоторые дополнительные замечания, чтобы прояснить, что наряду с парой других факторов, при выборе между условиями предварительной проверки (известными как LBYL или «Посмотри, прежде чем прыгать») и просто обработкой исключений (известных как EAFP или «Легче попросить прощения, чем разрешения») имеют значение 4 раза.»
Эти тайминги:
- Время, когда проверка завершается успешно с помощью LBYL
- Время, когда проверка завершается неудачей с помощью LBYL
- Время, когда исключение не генерируется с помощью EAFP
- Время, когда с помощью EAFP создается исключение
Дополнительными факторами являются:
- Типичное соотношение случаев успешной проверки / сбоя или исключения, вызванного / не вызванного
- Существует ли условие гонки, которое предотвращает использование LBYL
Этот последний момент необходимо решить в первую очередь: если существует вероятность возникновения состояния гонки, то у вас нет выбора, вы должны использовать обработку исключений. Классическим примером является:
if <dir does not exist>:
<create dir> # May still fail if another process creates the target dir
Поскольку LBYL не исключает исключения в таких случаях, это не дает никакой реальной выгоды, и не нужно принимать решения: EAFP — это единственный подход, который будет корректно обрабатывать условия гонки.
Но если нет условия гонки, любой подход потенциально жизнеспособен. Они предлагают разные компромиссы:
- если исключение не вызвано, то EAFP близок к свободному
- однако, если возникает исключение, это сравнительно дорого, поскольку требуется довольно много обработки, связанной с развертыванием стека, созданием исключения и сравнением его с предложениями обработки исключений
- LBYL, напротив, сопряжен с потенциально высокими фиксированными затратами: дополнительная проверка выполняется всегда, независимо от успеха или неудачи
Это затем приводит к следующим критериям принятия решения:
- Известно ли, что этот фрагмент кода имеет решающее значение для скорости приложения? Если нет, то не беспокойтесь о том, какой из двух быстрее, беспокойтесь о том, какой из двух легче читать.
- Является ли предварительная проверка более дорогостоящей, чем стоимость создания и перехвата исключения? Если да, то EAFP всегда быстрее и его следует использовать.
- Все становится интереснее, если ответ «нет». В этом случае, что быстрее, будет зависеть от того, является ли случай успеха или ошибки более распространенным, а также от относительных скоростей предварительной проверки и обработки исключений. Для окончательного ответа на этот вопрос требуются измерения реального времени.
Как грубое эмпирическое правило:
- если существует потенциальное условие гонки, используйте EAFP
- если скорость не критична, просто используйте то, что, по вашему мнению, легче читать
- если предварительная проверка обходится дорого, используйте EAFP
- если вы ожидаете, что операция в большинстве случаев будет успешной *, используйте EAFP
- если вы ожидаете, что операция завершится неудачно более чем в половине случаев, используйте LBYL
- если сомневаетесь, измерьте это
* Люди будут по-разному относиться к тому, что они считают «большую часть времени» в этом контексте. Что касается меня, если я ожидаю, что операция будет успешной более чем в половине случаев, я бы просто использовал EAFP как нечто само собой разумеющееся, пока у меня не возникнет оснований подозревать, что этот фрагмент кода действительно является узким местом в производительности.
Комментарии:
1. Вау, это один из лучших ответов SO, которые я когда-либо получал! Я думаю, что я слишком сильно полагался на классы, чтобы постоянно выполнять тяжелую работу, поскольку теперь, когда я задумываюсь об этом, я выполняю векторное / скалярное сложение только один раз, поэтому, если бы я перенес всю проверку типов за пределы класса в требуемые области кода, я мог бы устранить в основном все связанные с классом задержки в моем коде.
2. @Blender: «переместите всю проверку типов за пределы класса». Универсально верно. Не только для производительности, но и для простоты в целом. Классы просто работают, они не проверяют.
3. Привет. Я не совсем понимаю последний пункт С. Лотта о переносе проверки типов за пределы класса. Как это удаляет строки кода? Как я предполагаю, вам тогда придется выполнять проверку типа везде, где вы используете класс, а не просто делать это один раз в классе.
4. @Jonathon: Я полагаю, что его точка зрения заключается в том, что, помимо явных процедур проверки, часто лучше следовать правилам «Мусор в мусор из» и просто позволять собственным исключениям Python избегать, если люди передают вам бессмысленные аргументы. Python довольно хорош в том, чтобы убедиться, что вы получите исключение в таких случаях вместо того, чтобы молча создавать мусорные данные — только в (относительно редких) последних случаях и случаях, когда ошибки в противном случае сбивают с толку, вам нужно встроить собственную проверку в операции.
5. Недавно мне стало любопытно по этому поводу, поэтому я провел несколько экспериментов. Результаты: gerg.ca/blog/post/2015/try-except-speed . Краткая версия: то, что сказал @ncoghlan, т. Е. исключения дешевле, пока ошибки редки.
Ответ №2:
В Python исключения часто выполняются быстрее из-за меньшего количества поисковых запросов. Однако один друг однажды сказал (и это должно относиться к любому языку), что каждый раз, когда перехватывается исключение, происходит небольшая задержка. Избегайте использования исключений, где задержка может быть проблемой.
В приведенном вами примере я бы выбрал исключение.
Комментарии:
1. Я работаю с raytracer; иногда я получаю вывод
0.0
, в то время как в других случаях я получаюVector(1, 0, 1.5)
. Мне нужно знать тип, поскольку каждый случай обрабатывается по-разному. Итак, вы рекомендуете мне не делатьtry...except
этого?2. Ну, я изменил свои
try
блоки наif
blocks, и я получил увеличение скорости в два раза! Спасибо!