Perché l'uso di `o` all'interno di una clausola salvo non causa un SyntaxError? C'è un uso valido per questo?


11

Al lavoro, mi sono imbattuto in una exceptclausola con un oroperatore:

try:
    # Do something.
except IndexError or KeyError:
    # ErrorHandling

So che le classi di eccezione dovrebbero essere passate come una tupla, ma mi ha infastidito il fatto che non causerebbe nemmeno a SyntaxError.

Quindi, prima ho voluto indagare se funziona davvero. E non lo fa.

>>> def with_or_raise(exc):
...     try:
...         raise exc()
...     except IndexError or KeyError:
...         print('Got ya!')
...

>>> with_or_raise(IndexError)
Got ya!

>>> with_or_raise(KeyError)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in with_or_raise
KeyError

Quindi non ha colto la seconda eccezione e, guardando il bytecode, diventa più chiaro il perché:

>>> import dis
>>> dis.dis(with_or_raise)
  2           0 SETUP_EXCEPT            10 (to 12)

  3           2 LOAD_FAST                0 (exc)
              4 CALL_FUNCTION            0
              6 RAISE_VARARGS            1
              8 POP_BLOCK
             10 JUMP_FORWARD            32 (to 44)

  4     >>   12 DUP_TOP
             14 LOAD_GLOBAL              0 (IndexError)
             16 JUMP_IF_TRUE_OR_POP     20
             18 LOAD_GLOBAL              1 (KeyError)
        >>   20 COMPARE_OP              10 (exception match)
             22 POP_JUMP_IF_FALSE       42
             24 POP_TOP
             26 POP_TOP
             28 POP_TOP

  5          30 LOAD_GLOBAL              2 (print)
             32 LOAD_CONST               1 ('Got ya!')
             34 CALL_FUNCTION            1
             36 POP_TOP
             38 POP_EXCEPT
             40 JUMP_FORWARD             2 (to 44)
        >>   42 END_FINALLY
        >>   44 LOAD_CONST               0 (None)
             46 RETURN_VALUE

Quindi, possiamo vedere, l'istruzione 14 prima carica la IndexErrorclasse nello stack. Quindi controlla se quel valore è True, cosa che è a causa della verità di Python e infine passa direttamente all'istruzione 20 dove exception matchviene fatto. Poiché l'istruzione 18 è stata ignorata, KeyErrornon è mai stata caricata nello stack e quindi non corrisponde.

Ho provato con Python 2.7 e 3.6, stesso risultato.

Ma allora, perché è una sintassi valida? Immagino che sia uno dei seguenti:

  1. È un artefatto di una versione davvero vecchia di Python.
  2. In realtà esiste un caso d'uso valido per l'utilizzo orall'interno di una exceptclausola.
  3. È semplicemente una limitazione del parser Python che potrebbe dover accettare qualsiasi espressione dopo la exceptparola chiave.

Il mio voto è sul 3 (dato che ho visto alcune discussioni su un nuovo parser per Python) ma spero che qualcuno possa confermare quell'ipotesi. Perché se fosse 2 per esempio, voglio sapere quel caso d'uso!

Inoltre, sono un po 'all'oscuro di come continuare questa esplorazione. Immagino che dovrei scavare nel codice sorgente del parser CPython ma idk dove trovarlo e forse c'è un modo più semplice?

Risposte:


7

In except e, epuò essere presente qualsiasi espressione Python valida:

try1_stmt ::=  "try" ":" suite
               ("except" [expression ["as" identifier]] ":" suite)+
               ...

[..] Per una exceptclausola con un'espressione, tale espressione viene valutata e la clausola corrisponde all'eccezione se l'oggetto risultante è "compatibile" con l'eccezione. Un oggetto è compatibile con un'eccezione se è la classe o una classe base dell'oggetto eccezione o una tupla contenente un elemento compatibile con l'eccezione.

https://docs.python.org/3/reference/compound_stmts.html#the-try-statement

L'espressione IndexError or KeyErrorproduce il valore IndexError. Quindi questo equivale a:

except IndexError:
   ...

Grazie per la risposta rapida all'illuminazione! Considereremmo una limitazione del parser (ovvero essere in grado di accettare qualsiasi espressione piuttosto che una più specifica) o una scelta deliberata per non limitare troppo le cose?
Loïc Teixeira,

1
Mi sembrerebbe attenersi ai principi di base di Python per semplificare il miglioramento. Perché inventare nuove regole che possono essere limitanti solo quando accettare qualsiasi espressione significa piena libertà senza nuovi casi speciali?
Inganno

È una scelta deliberata, che consente di riunire una tupla di eccezioni da catturare dinamicamente e di usare un tale valore in exceptun'istruzione.
user4815162342

Immagino che il motivo per limitare le possibilità sarebbe impedire agli sviluppatori di scrivere codice che non fa ciò che intendevano. Dopotutto, scrivere except IndexError or KeyErrorsembra una cosa decente da scrivere. Tuttavia sono d'accordo con te sul fatto che sarebbe contro alcuni altri valori che Python cerca di rispettare.
Loïc Teixeira,

Si noti che anche Python lo consente var == 1 or 2, che ad un occhio non allenato "sembra una cosa decente da scrivere".
Eric

-2

Dovresti usare una n-tupla di tipi anziché un'espressione logica (che restituisce solo il primo elemento non falso):

def with_or_raise(exc):
  try:
    raise exc()
  except (IndexError,KeyError):
    print('Got ya!')

Come indicato nella domanda, so che le classi di eccezione dovrebbero essere passate come tupla, ma mi chiedevo perché usare orPython fosse ancora valido.
Loïc Teixeira,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.