[Python 3] Cum as putea face ca sys.exit(1) sa cheme la randul ei ValueError, cand insasi sys.exit(1) a fost chemata de catre ValueError?

Salutare! Acest thread e o continuare al acestuia, rezolvat nu cu mult timp in urma. Insa acum vin cu o problema la ca care n-am gasit nicio solutie dupa o cautare de cateva ore.

Din nou, am urmatoarele doua fisiere, exemplu.py si test_exemplu.py:


# exemplu.py

import argparse, sys

def parse_args(args):
    parser = argparse.ArgumentParser()
    parser.add_argument("-c", "--cale", type=str, metavar=" ")
    parser.add_argument("-t", "--timp", type=float, metavar=" ")
    return parser.parse_args(args)

def validate(data):
    try:
        if data.timp < 0:            
            raise ValueError
    except ValueError:
        print(f"Timpul are o valoare negativa: {data.timp}. Te rog introdu o valoare pozitiva")
        sys.exit(1)
    
def main():
    parsed_data = parse_args(sys.argv[1:])
    validate(parsed_data)

    print(parsed_data.cale)
    print(parsed_data.timp)

if __name__ == '__main__':
    main() 

# test_exemplu.py

import unittest
from exemplu import parse_args, validate

class TestExemplu(unittest.TestCase):
    def test_parser(self):       
        parser = parse_args(['-c', 'asd', '-t', '-1'])
        self.assertEqual(parser.cale,'asd')
        self.assertEqual(parser.timp, -1)    
    
    def test_valoare_negativa(self):
       parsed_data = parse_args(['-t', '-1'])
       self.assertRaises(ValueError, validate, parsed_data)

if __name__ == '__main__':
    unittest.main()       

Dupa cum puteti vedea, in exemplu.py, in functia validate, am customizat mesajul pentru ValueError, iar apoi am folosit sys.exit(1) pentru a inchide programul. Totul functioneaza bine la rularea lui exemplu.py, insa, cand vine vorba de rularea lui test_exemplu.py, imi aparea urmatoarea eroare pentru cel de-al doilea test (test_valoare_negativa):

test_parser (test_exemplu.TestExemplu) ... ok
test_valoare_negativa (test_exemplu.TestExemplu) ... Timpul are o valoare negativa: -1.0. Te rog introdu o valoare pozitiva
ERROR

======================================================================
ERROR: test_valoare_negativa (test_exemplu.TestExemplu)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\tester\Downloads\cli_mon_tool\exemplu.py", line 14, in validate
    raise ValueError
ValueError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\tester\Downloads\cli_mon_tool\test_exemplu.py", line 14, in test_valoare_negativa
    self.assertRaises(ValueError, validate, parsed_data)
  File "C:\Users\tester\AppData\Local\Programs\Python\Python310\lib\unittest\case.py", line 738, in assertRaises    
    return context.handle('assertRaises', args, kwargs)
  File "C:\Users\tester\AppData\Local\Programs\Python\Python310\lib\unittest\case.py", line 201, in handle
    callable_obj(*args, **kwargs)
  File "C:\Users\tester\Downloads\cli_mon_tool\exemplu.py", line 17, in validate
    sys.exit(1)
SystemExit: 1

----------------------------------------------------------------------
Ran 2 tests in 0.009s

FAILED (errors=1)

Asadar, cum as putea face ca sys.exit(1) sa cheme la randul ei ValueError, cand insasi sys.exit(1) a fost chemata de catre ValueError? Sau ce alternative as avea?

Va multumesc!

Solutia pe care am gasit-o este bazata pe urmatoarele 2 link-uri:

  1. python - Hide traceback unless a debug flag is set - Stack Overflow
  2. Sorting out http://stackoverflow.com/questions/27674602/hide-traceback-unless-a-debug-flag-is-set · GitHub

Se pare ca codul de mai jos face exact ceea ce imi doream:


# exemplu.py

import argparse, sys
debug = False

def parse_args(args):
    parser = argparse.ArgumentParser()
    parser.add_argument("-c", "--cale", type=str, metavar=" ")
    parser.add_argument("-t", "--timp", type=float, metavar=" ")
    return parser.parse_args(args)

def exceptionHandler(exception_type, exception, traceback, debug_hook=sys.excepthook):
    '''Print user friendly error messages normally, full traceback if DEBUG on.
       Adapted from http://stackoverflow.com/questions/27674602/hide-traceback-unless-a-debug-flag-is-set
    '''
    if debug:
        print('\n*** Error:')        
        debug_hook(exception_type, exception, traceback)
    else:        
        print("%s: %s" % (exception_type.__name__, exception))   
sys.excepthook = exceptionHandler 

def validate(data):
    try:
        if data.timp < 0:            
            raise ValueError
    except ValueError:
        raise ValueError(f"Timpul are o valoare negativa: {data.timp}. Te rog introdu o valoare pozitiva")
    try:
        if data.timp == 0:            
            raise ValueError
    except ValueError:
        raise ValueError(f"Timpul are valoarea zero. Te rog introdu o valoare pozitiva")    
            
def main():
    parsed_data = parse_args(sys.argv[1:])
    validate(parsed_data)

    print(parsed_data.cale)
    print(parsed_data.timp)

if __name__ == '__main__':
    main()    

# test_exemplu.py

import unittest
from exemplu import parse_args, validate

class TestExemplu(unittest.TestCase):
    def test_parser(self):       
        parser = parse_args(['-c', 'asd', '-t', '-1'])
        self.assertEqual(parser.cale,'asd')
        self.assertEqual(parser.timp, -1)    
    
    def test_valoare_negativa(self):
        parsed_data1 = parse_args(['-t', '-1'])
        parsed_data2 = parse_args(['-t', '0'])
        parsed_data3 = parse_args(['-t', '1'])
        self.assertRaises(ValueError, validate, parsed_data1)
        self.assertRaises(ValueError, validate, parsed_data2)
        try:        
            self.assertRaises(ValueError, validate, parsed_data3)
        except:
            pass           

if __name__ == '__main__':
    unittest.main()   

:slight_smile:
E bine ca iti place sa cauti rezolvari la probleme, dar sincer, te cam invarti in jurul cozii.
sa faci raise (pe ambele blocuri) intr-un try-except e cam dubios.

Sa testezi cod cu exceptii e greu, de aici nevoia de mecanisme de introspectie/runtime hooks/reflection.
Incearca sa faci un pas in spate si sa te gandesti daca chiar ai nevoie de exceptii, sau cum ai rescrie codul asta daca Python nu ar avea mecanism de exceptii.

Daca poti semnala cumva eroarea prin alte mecanisme (hint: valori), e mult mai usor de testat fara mecanisme complexe.

1 Like

Iti multumesc pentru sfaturi! Din pacate, nicicare dintre termenii pe care i-ai folosit nu-mi suna cunoscuti. O sa incerc sa aplic mecanismele pe viitor, pentru ca deocamdata incerc sa ma descurc cu ceea ce stiu, din moment ce timpul e problema.

Pattern-ul asta e super-dubios. In principiu exceptiile le folosesti pentru non-local control flows. Mai pe romaneste, ai ajuns intr-un punct al programului si ai o situatie exceptionala (data.timp < 0). Si nici nu prea ai ce sa faci acolo ca sa o rezolvi (eg - sa-i ceri user-ului un al timp). Asa ca faci un raise ValueError si lasi o alta bucata de cod, undeva intr-un loc mult mai high-level sa se ocupe de ea.

Cum as rescrie ce e mai sus:

def validate(data):
    if data.timp < 0:            
        raise ValueError(f"Timpul are o valoare negativa: {data.timp}. Te rog introdu o valoare pozitiva")

...
# in main
def main():
   #your codes here
  try:
     validate(parsed_data)
  except ValueError as e:
     print(f"Some invalid data -- {e}")
     sys.exit(1)
  # rest of code which now knows parsed_data is OK
1 Like

Eu nu sunt un specialist in Python dar daca tot faci exit la eroare de argument nu-i mai simplu fara exceptii?

import argparse, sys

def check_time(v):
  fv=float(v)
  if fv < 0:
    sys.exit("Ups! Bad time value.")
  return fv

parser = argparse.ArgumentParser("parser")
parser.add_argument('-time', type=check_time, help="Insert the time.")
args = parser.parse_args()

print(args.time)
1 Like

Multumesc de idee! O sa incerc sa folosesc metoda ta.

Am nevoie sa fac unit test, e o cerinta, altfel banuiesc ca as putea face asa. As dori sa spun mai multe, dar din pacate nu cunosc bine terminologia si poate as spune prostii.

Yep, that’s the way to go – si in plus, incearca sa si reflectezi un pic concentrat pe ce a explicat omul, e o practica foarte buna indiferent de limbajul in care scrii.

Aruncatorul de exceptii deseori ajunge in situatia de a arunca tocmai fiindca nu stie cum ar trebui sa procedeze. Motiv pentru care nu trebuie sa logheze in stdout (cum aveai tu inainte, print + raise) tocmai fiindca nu stie daca aplicatia logheaza in stdout, in fisier, pe retea sau in vreun device dubios.

Cine prinde, in schimb, vede exact ce s-a aruncat, eventual un cod de eroare sau alta proprietate a exceptiei, stie din ce bucata de cod s-a aruncat, si are tot contextul sa ia decizia potrivita: reincearca instant, pune intr-o coada sa incerce mai tarziu, cere alt input la user, logheaza unde stie, face crash la aplicatie, etc.

Astfel obtii o mult mai buna distribuire a responsabilitatii intre functii, module, servicii, etc.

Spor!

2 Likes

Iti multumesc! Insa sunt nevoit sa revin mai tarziu la ceea ce ai spus, cand o sa dobandesc mai multe cunostinte.

Iti multumesc din nou! Am folosit metoda ta, cu exceptia acesteia.

Dupa ce am mai exersat putin, am inceput sa vad de ce arata dubios.