Veelgestelde vragen over Python: "Hoe snel zijn uitzonderingen?"

Ik keek alleen maar naar de Python FAQ omdat deze in een andere vraag werd genoemd. Ik heb er nooit eerder gedetailleerd naar gekeken, ik kwam deze vraag : "Hoe snel zijn uitzonderingen?":

Een try/except-blok is uiterst efficiënt. Een uitzondering vangen is duur. In versies van Python vóór 2.0 was het gebruikelijk om dit idioom te gebruiken:

  proberen:
    value = mydict [key]
behalve KeyError:
    mydict [key] = getvalue (sleutel)
    value = mydict [key]
 

Ik was een beetje verbaasd over het "vangen van een uitzondering is duur" deel. Verwijst dit alleen naar die behalve gevallen waarin u de uitzondering eigenlijk opslaat in een variabele, of in het algemeen alle behalve s (inclusief die in het voorbeeld hierboven)?

Ik heb altijd gedacht dat het gebruik van dergelijke uitdrukkingen erg pythisch zou zijn, vooral omdat het in Python "gemakkelijker is om vergiffenis te vragen dan om toestemming te krijgen" . Ook veel antwoorden op SO volgen over het algemeen dit idee.

Zijn de prestaties om uitzonderingen op te vangen echt zo slecht? Moet men in plaats daarvan LBYL ("Look before you leap") eerder volgen?

(Houd er rekening mee dat ik het niet direct heb over het voorbeeld in de Veelgestelde vragen; er zijn veel andere voorbeelden waarbij u alleen op een uitzondering let in plaats van eerder de typen te controleren.)

13

3 antwoord

Uitzonderingen incasseren is duur, maar uitzonderingen moeten uitzonderlijk zijn (lees, gebeurt niet vaak). Als uitzonderingen zeldzaam zijn, is try/catch sneller dan LBYL.

Het volgende voorbeeld keer een opzoeking op een woordenboeksleutel met behulp van uitzonderingen en LBYL wanneer de sleutel bestaat en wanneer deze niet bestaat:

import timeit

s = []

s.append('''\
try:
    x = D['key']
except KeyError:
    x = None
''')

s.append('''\
x = D['key'] if 'key' in D else None
''')

s.append('''\
try:
    x = D['xxx']
except KeyError:
    x = None
''')

s.append('''\
x = D['xxx'] if 'xxx' in D else None
''')

for i,c in enumerate(s,1):
    t = timeit.Timer(c,"D={'key':'value'}")
    print('Run',i,'=',min(t.repeat()))

uitgang

Run 1 = 0.05600167960596991       # try/catch, key exists
Run 2 = 0.08530091918578364       # LBYL, key exists (slower)
Run 3 = 0.3486251291120652        # try/catch, key doesn't exist (MUCH slower)
Run 4 = 0.050621117060586585      # LBYL, key doesn't exist

Wanneer het gebruikelijke geval geen uitzondering is, is try/catch "uiterst efficiënt" in vergelijking met LBYL.

19
toegevoegd
"uitzonderingen moeten uitzonderlijk zijn" is een goede mantra. Ik vergeet het te vaak.
toegevoegd de auteur kevinarpe, de bron
Zowat wat ik ging zeggen. Het gebruikt uitzonderingen om uw normale programmastroom te controleren die erg duur is.
toegevoegd de auteur Tony Hopkinson, de bron
Als je wat snelle wiskunde doet, geven de cijfers aan dat try/except alleen mag worden gebruikt als je verwacht dat de sleutel niet hooguit 8,95% van de tijd zal worden gevonden (ongeveer 1 op de 11 oproepen). Ik heb geen ruimte om de vergelijking te berekenen die ik heb gebruikt, maar: laat cbn = kosten van de vertakkingsmethode in normaal geval, cbx = kosten van de takmethode in uitzonderlijke gevallen, < code> ctn = kosten van try/behalve in normaal geval, ctx = kosten van try/behalve in uitzonderlijk geval, en px = kans dat een uitzonderlijk geval optreedt; dan voor alle px <= (ctn-cbn)/(ctn-ctx + cbx-cbn) , zal de try/except-methode op de lange termijn sneller zijn.
toegevoegd de auteur Eli Collins, de bron
Gewoon een addendum - aangezien het meten en berekenen van dit soort dingen op zich al tijdrovend wordt, probeer ik een vuistregel voor python te gebruiken dat iets dat meer dan 1/8 keer voorkomt geen "uitzonderlijk" geval is, en zou waarschijnlijk een andere methode moeten gebruiken - wat ook nuttig is gebleken bij het ontwerpen van een api, evenals het schrijven van code.
toegevoegd de auteur Eli Collins, de bron

De kosten zijn uiteraard afhankelijk van de implementatie, maar ik maak me er geen zorgen over. Het zal hoe dan ook niet van belang zijn. Standaardprotocollen brengen uitzonderingen op de vreemdste plaatsen naar voren (denk aan StopIteration ), dus je bent omringd door verhogen en vangen, of je het nu leuk vindt of niet.

Bij het kiezen tussen LBYL en EAFP, maak je je zorgen over de leesbaarheid van de code, in plaats van te focussen op micro-optimalisaties. Ik zou type-controle vermijden indien mogelijk, omdat dit de algemeenheid van de code zou kunnen verminderen.

6
toegevoegd
Ja, vermijd voortijdige optimalisatie.
toegevoegd de auteur Brecht Machiels, de bron

Als het geval dat de sleutel niet wordt gevonden, meer dan uitzonderlijk is, stel ik voor de 'get'-methode te gebruiken, die in alle gevallen een constante snelheid biedt:

s.append('''\
x = D.get('key', None)
''')

s.append('''\
x = D.get('xxx', None)
''')
0
toegevoegd
U kunt in plaats daarvan dict.setdefault gebruiken. Maar ja, de code was slechts een eenvoudig voorbeeld uit de FAQ.
toegevoegd de auteur poke, de bron
Sorry, eigenlijk is het langzamer.
toegevoegd de auteur Johan Boulé, de bron