Try / catch czy validation for speed?

Pracuję z Pythonem i za każdym razem, gdy musiałem zweryfikować dane wejściowe funkcji, zakładałem, że dane wejściowe działają, a następnie wyłapywałem błędy.

W moim przypadku miałem uniwersalną Vector() klasę, której używałem do kilku różnych rzeczy, z których jedną jest dodawanie. Funkcjonował zarówno jako klasa Color(), jak i jako Vector(), więc kiedy dodam Skalar do Color(), powinien dodać tę stałą do każdego składnika. Vector() i Vector() Dodawanie wymaga dodawania komponentów.

Ten kod jest jest używany do raytracera, więc wszelkie dopalacze prędkości są świetne.

Oto uproszczona wersja mojej klasy 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)

Obecnie używam metody try...except. Czy ktoś zna szybszą metodę?


EDIT: Dzięki odpowiedziom wypróbowałem i przetestowałem następujące rozwiązanie, które sprawdza konkretnie nazwę klasy przed dodaniem obiektów 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)

Przeprowadziłem test prędkości z tymi dwoma blokami kodu używając timeit, a wyniki były dość znaczące:

 1.0528049469 usec/pass for Try...Except
 0.732456922531 usec/pass for If...Else
 Ratio (first / second): 1.43736090753

Nie testowałem Vector() klasy z bez w ogóle walidacji wejściowej (tj. przeniesienia sprawdzania z klasy i do rzeczywistego kodu), ale wyobrażam sobie, że jest to jeszcze szybsze niż metoda if...else.


Późna aktualizacja: patrząc wstecz na ten kod, jest to , a nie optymalne rozwiązanie.

OOP robi to jeszcze szybciej:

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)
Author: Nathan Kleyn, 2011-04-08

2 answers

Podniosłem odpowiedź Matta Joinera, ale chciałem dodać kilka dodatkowych uwag, aby wyjaśnić, że wraz z kilkoma innymi czynnikami, istnieją 4 czasy, które mają znaczenie przy wyborze między Warunkami wstępnego sprawdzania (znanymi jako LBYL lub "spójrz, zanim skoczysz") a Obsługą WYJĄTKÓW (znanymi jako EAFP lub "łatwiej prosić o przebaczenie niż pozwolenie").

Te czasy to:

  • czas kiedy Kontrola powiedzie się z LBYL
  • czas, kiedy sprawdź nie działa z LBYL
  • Czas gdy wyjątek nie jest wyrzucony z EAFP
  • Czas gdy wyjątek jest wyrzucony z EAFP

Dodatkowymi czynnikami są:

  • typowy stosunek sukcesu/porażki sprawdzania lub WYJĄTKÓW rzuconych / nie rzuconych przypadków
  • czy istnieje stan rasy, który uniemożliwia stosowanie LBYL

Ten ostatni punkt to ten, który należy rozwiązać najpierw: jeśli istnieje potencjał w przypadku rasy nie masz wyboru, musisz używać obsługi wyjątków. Klasyczny przykład to:

if <dir does not exist>:
    <create dir> # May still fail if another process creates the target dir

Ponieważ LBYL nie wyklucza, że wyjątkiem są takie przypadki, nie przynosi realnych korzyści i nie ma potrzeby oceniania: EAFP jest jedynym podejściem, które poprawnie poradzi sobie z kondycją rasy.

Ale jeśli nie ma warunków rasowych, każde podejście jest potencjalnie realne. Oferują różne kompromisy:

  • jeśli nie zostanie podniesiony żaden wyjątek, to EAFP jest bliskie wolności
  • jest to jednak stosunkowo kosztowne, ponieważ istnieje sporo procesów związanych z rozwijaniem stosu, tworzeniem wyjątku i porównywaniem go z klauzulami obsługi wyjątków
  • LBYL, z kolei, ponosi potencjalnie wysokie koszty stałe: dodatkowa kontrola jest zawsze wykonywana, niezależnie od sukcesu lub porażki [11]}

To prowadzi do następujących kryteriów decyzyjnych:

  • czy ten fragment kodu jest znany aby być krytycznym dla szybkości aplikacji? Jeśli nie, to nie martw się, który z nich jest szybszy, martw się, który z nich jest łatwiejszy do odczytania.
  • czy kontrola wstępna jest droższa niż koszt podniesienia i złapania wyjątku? Jeśli tak, to EAFP jest zawsze szybszy i powinien być używany.
  • rzeczy stają się bardziej interesujące, jeśli odpowiedź brzmi "nie". W takim przypadku, który jest szybszy, zależy od tego, czy przypadek sukcesu lub błędu jest bardziej powszechny, a względne prędkości kontrola wstępna i obsługa wyjątków. Odpowiedź na to zdecydowanie wymaga rzeczywistych pomiarów czasu.

Jako ogólna zasada:

  • jeśli istnieje potencjalny stan rasy, Użyj EAFP
  • Jeśli prędkość nie jest krytyczna, po prostu użyj tego, co uważasz za łatwiejsze do odczytania
  • jeśli wstępna kontrola jest droga, użyj EAFP
  • jeśli oczekujesz, że operacja zakończy się sukcesem przez większość czasu*, użyj EAFP
  • Jeśli spodziewasz się, że operacja nie powiedzie się więcej niż połowę czasu, użyj LBYL
  • Jeśli masz wątpliwości, zmierz je

*ludzie będą się różnić co do tego, co uważają za "większość czasu" w tym kontekście. Dla mnie, jeśli spodziewam się, że operacja zakończy się sukcesem ponad połowę czasu, użyłbym EAFP jako rzecz jasna, dopóki nie miałem powodów podejrzewać, że ten fragment kodu jest faktycznym wąskim gardłem wydajności.

 79
Author: ncoghlan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-23 04:23:27

W Pythonie wyjątki są często szybsze ze względu na zmniejszoną liczbę wyszukiwań. Jednak przyjaciel powiedział kiedyś (i powinno to dotyczyć każdego języka), udawaj, że za każdym razem, gdy złapany jest wyjątek, występuje małe opóźnienie. Unikaj WYJĄTKÓW, w których opóźnienie może stanowić problem.

W podanym przez Ciebie przykładzie wybrałbym wyjątek.

 5
Author: Matt Joiner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-08 01:45:48