2017-09-09 1 views
2

Le script que j'écris devrait revenir à l'invite du shell avec un message utile si les données à traiter ne sont pas exactement correctes. L'utilisateur doit corriger les problèmes signalés jusqu'à ce que le script soit satisfait et ne quitte plus avec des messages d'erreur. Je développe le script avec TTD, donc j'écris un test pytest avant d'écrire la fonction. Le most heavily up-voted answer here suggère que les scripts soient édités en appelant sys.exit ou en soulevant SystemExit.Terminer un script de ligne de commande Python avec des messages utiles, pythoniquement

La fonction:

passe ce test (où _non-text.png est un fichier PNG, à savoir, non encodé en UTF-8):

def test_istext(): 
    with pytest.raises(SystemExit): 
     istext('_non-text.png') 

Cependant, le script continue de fonctionner, et les instructions placées après l'exécution du bloc try/except. Je veux que le script se termine complètement à chaque fois pour que l'utilisateur puisse déboguer les données jusqu'à ce qu'elles soient correctes, et le script fera ce qu'il est censé faire (c'est-à-dire traiter un répertoire plein de texte UTF-8 fichiers, pas PNG, JPG, PPTX ... fichiers).

a également essayé:

qui suit passe également le test ci-dessus en soulevant une exception qui est une sous-classe de SystemExit, mais il fait aussi quitter pas le script:

def istext(file_to_test): 
    class NotUTF8Error(SystemExit): pass 
    try: 
     open(file_to_test).read(512) 
    except UnicodeDecodeError: 
     raise NotUTF8Error('File {} must be UTF-8.'.format(file_to_test)) 

Répondre

1

Vous pouvez utiliser la syntaxe raise Exception from exception:

class MyException(SystemExit): 
    pass 


def istext(file_to_test): 
    try: 
     open(file_to_test).read(512) 
    except UnicodeDecodeError as exception: 
     raise MyException(f'File {file_to_test} must be encoded in UTF-8 (Unicode); try converting.') \ 
      from exception 

je ce cas, vous ne change pas un message d'erreur d'origine et d'ajouter votre propre message.

+0

Hmm, je n'ai pas pu obtenir cet exemple pour fonctionner, même sans le message d'erreur. –

+0

Après avoir déclaré la classe 'class NotUTF8Error (SystemExit): pass', et après' exception UnicodeDecodeError comme exception: ',' ​​raise NotUTF8Error from exception' obtient un 'SyntaxError'. –

+0

Peu importe - j'ai réussi à faire fonctionner cela, seulement maintenant le pytest échoue avec 'NameError: name 'NotUTF8Error' n'est pas défini'. –

0

Le try ... except block est pour attraper une erreur et la gérer en interne. Ce que vous voulez faire est de re-augmenter l'erreur.

def istext(file_to_test): 
try: 
    open(file_to_test).read(512) 
except UnicodeDecodeError: 
    print(('File {} must be encoded in UTF-8 (Unicode); try converting.'.format(file_to_test))) 
    raise 

Ceci affichera votre message, puis relancera automatiquement l'erreur que vous avez détectée.

Au lieu de simplement sur-relancer l'ancienne erreur, vous pouvez également modifier le type d'erreur. Dans ce cas, vous spécifiez d'augmenter encore, par exemple .:

raise NameError('I'm the shown error message') 
+0

Mon premier instinct a été de créer une exception personnalisée le long des lignes de 'class NotUTF8Error (SystemExit):'. Donc vous suggérez qu'après 'except UnicodeDecodeError:' je voudrais 'NotUTF8Error (" Some message ")'? –

+0

Exactement. Ou vous spécifiez déjà dans la définition de votre classe d'erreur ce que le texte va dire, de sorte qu'aucune relance n'est nécessaire. – Sudix

+0

J'ai essayé la deuxième suggestion (voir la question modifiée ci-dessus), mais comme ma tentative originale, elle passe la pyest mais ne quitte pas le script. La première suggestion (re-raise 'UnicodeDecodeError' après l'impression du message) passe un pytest modifié mais sans quitter le script. –

0

Votre problème n'est pas de savoir comment quitter un programme (sys.exit() fonctionne très bien). Votre problème est que votre scénario de test n'élève pas un UnicodeDecodeError.

Voici une version simplifiée de votre exemple. Cela fonctionne comme prévu:

import pytest 
import sys 

def foo(n): 
    try: 
     1/n 
    except ZeroDivisionError as e: 
     sys.exit('blah') 

def test_foo(): 
    # Assertion passes. 
    with pytest.raises(SystemExit): 
     foo(0) 
    # Assertion fails: "DID NOT RAISE <type 'exceptions.SystemExit'>" 
    with pytest.raises(SystemExit): 
     foo(9) 

Ajoutez une impression de diagnostic à votre code pour en savoir plus.Par exemple:

def istext(file_to_test): 
    try: 
     content = open(file_to_test).read(512) 
     # If you see this, no error occurred. Maybe your source 
     # file needs different content to trigger UnicodeDecodeError. 
     print('CONTENT len()', len(content)) 
    except UnicodeDecodeError: 
     sys.exit('blah') 
    except Exception as e: 
     # Maybe some other type of error should also be handled? 
     ... 
+0

Ceci clarifie utilement que pytest ne teste pas si '1/0' soulève' ZeroDivisionError', mais si 'sys.exit' soulève' SystemExit'. Merci! –

0

En fin de compte, ce qui a fonctionné est semblable à ce que @ADR proposé, avec une différence: je ne pouvais pas obtenir la syntaxe de chaîne formatée ci-dessus fonctionne correctement (f'File {file_to_test} must...'), ni ce que je pourrais trouver la documentation du préfixe f pour les chaînes.

Ma solution un peu moins élégante, puis, pour la fonction (renommé):

def is_utf8(file): 
    class NotUTF8Error(SystemExit): pass 
    try: 
     open(file).read(512) 
    except UnicodeDecodeError as e: 
     raise NotUTF8Error('File {} not UTF-8: convert or delete, then retry.'.format(file)) from e 

passe le pytest:

def test_is_utf81(): 
    with pytest.raises(SystemExit): 
     is_utf8('/Users/tbaker/github/tombaker/mklists/mklists/_non-text.png') 
+0

Êtes-vous sérieux? Vous n'aimez plus ma solution parce que j'utilise une simple fonctionnalité de Python 3.6? https://docs.python.org/3/whatsnew/3.6.html https://www.python.org/dev/peps/pep-0498/ – ADR

+0

@ADR merci pour la référence - Mes excuses - J'ai cherché cette caractéristique, mais ne l'a pas trouvé; cela n'a pas fonctionné pour moi parce que j'ai Python 3.5.2. Je retire l'acceptation et j'accepte votre réponse! –