Jeśli jednak nie może się połączyć, to db nie będzie istnieć dalej - dlatego ustawiłem db = None nad. Czy to jednak dobra praktyka?
Nie, ustawienie db = None nie jest najlepszą praktyką. Istnieją dwie możliwości, albo połączenie z bazą danych będzie działać, albo nie.
-
Połączenie z bazą danych nie działa:
Ponieważ zgłoszony wyjątek został przechwycony i nie został ponownie zgłoszony, kontynuujesz, dopóki nie osiągniesz
cursor = db.Cursor().db == None, więc wyjątek przypominającyTypeError: 'NoneType' object has no attribute 'Cursor'zostanie podniesiony. Ponieważ wyjątek generowany, gdy połączenie z bazą danych nie powiodło się, zostało już przechwycone, przyczyna niepowodzenia jest zamaskowana.Osobiście zawsze zgłaszam wyjątek dotyczący połączenia, chyba że zamierzasz wkrótce spróbować ponownie. Jak to złapiesz, zależy od ciebie; jeśli błąd będzie się powtarzał, piszę e-mailem "idź i sprawdź bazę danych".
-
Połączenie z bazą danych działa:
Zmienna
dbjest przypisany w twoimtry:... exceptblok. Jeśliconnectmetoda działa wtedydbzostanie zastąpiony obiektem połączenia.
Tak czy inaczej, początkowa wartość db nigdy nie jest używany.
Jednak słyszałem, że używanie obsługi wyjątków do kontroli przepływu, jak to jest złą praktyką.
W przeciwieństwie do innych języków Python robi użyj obsługi wyjątków do kontroli przepływu. Na końcu mojej odpowiedzi podałem link do kilku pytań dotyczących przepełnienia stosu i programistów, które zadają podobne pytanie. W każdym przykładzie zobaczysz słowa „ale w Pythonie”.
Nie oznacza to, że powinieneś przesadzać, ale Python generalnie używa mantry EAFP, "Łatwiej prosić o wybaczenie niż o pozwolenie." Trzy najczęściej głosowane przykłady w artykule Jak sprawdzić, czy istnieje zmienna? są dobrymi przykładami tego, jak można używać kontroli przepływu lub nie.
Czy zagnieżdżanie wyjątków to dobry pomysł? A może istnieje lepszy sposób radzenia sobie z zależnymi/kaskadowymi wyjątkami, takimi jak ten?
Nie ma nic złego w zagnieżdżonych wyjątkach, o ile robisz to rozsądnie. Rozważ swój kod. Możesz usunąć wszystkie wyjątki i zapakować całość w try:... except blok. Jeśli zostanie zgłoszony wyjątek, wiesz, co to było, ale trochę trudniej jest dokładnie wyśledzić, co poszło nie tak.
Co się wtedy stanie, jeśli chcesz sam powiedzieć e-mail w przypadku niepowodzenia cursor.execute ? Powinieneś mieć wyjątek wokół cursor.execute w celu wykonania tego jednego zadania. Następnie ponownie podnosisz wyjątek, aby został przechwycony w zewnętrznym try:... . Brak ponownego podbicia spowoduje, że kod będzie kontynuowany tak, jakby nic się nie wydarzyło, i jakąkolwiek logikę umieściłeś w swoim zewnętrznym try:... radzenie sobie z wyjątkiem zostanie zignorowane.
Ostatecznie wszystkie wyjątki są dziedziczone z BaseException .
Ponadto są pewne części (np. awarie połączenia), w których chciałbym, aby skrypt po prostu się zakończył - stąd zakomentowane wywołanie sys.exit().
Dodałem prostą klasę i sposób jej nazywania, czyli mniej więcej tak, jak zrobiłbym to, co próbujesz zrobić. Jeśli ma to być uruchomione w tle, drukowanie błędów nie jest warte zachodu - ludzie nie będą tam siedzieć ręcznie, szukając błędów. Powinni oni być zalogowani w dowolny sposób, a odpowiednie osoby powiadomione. Z tego powodu usunąłem drukowanie i zastąpiłem je przypomnieniem o logowaniu.
Ponieważ podzieliłem klasę na wiele funkcji, gdy connect metoda kończy się niepowodzeniem i zgłaszany jest wyjątek execute wywołanie nie zostanie uruchomione, a skrypt zakończy się po próbie rozłączenia.
import cx_Oracle
class Oracle(object):
def connect(self, username, password, hostname, port, servicename):
""" Connect to the database. """
try:
self.db = cx_Oracle.connect(username, password
, hostname + ':' + port + '/' + servicename)
except cx_Oracle.DatabaseError as e:
# Log error as appropriate
raise
# If the database connection succeeded create the cursor
# we-re going to use.
self.cursor = self.db.cursor()
def disconnect(self):
"""
Disconnect from the database. If this fails, for instance
if the connection instance doesn't exist, ignore the exception.
"""
try:
self.cursor.close()
self.db.close()
except cx_Oracle.DatabaseError:
pass
def execute(self, sql, bindvars=None, commit=False):
"""
Execute whatever SQL statements are passed to the method;
commit if specified. Do not specify fetchall() in here as
the SQL statement may not be a select.
bindvars is a dictionary of variables you pass to execute.
"""
try:
self.cursor.execute(sql, bindvars)
except cx_Oracle.DatabaseError as e:
# Log error as appropriate
raise
# Only commit if it-s necessary.
if commit:
self.db.commit()
Następnie zadzwoń:
if __name__ == "__main__":
oracle = Oracle.connect('username', 'password', 'hostname'
, 'port', 'servicename')
try:
# No commit as you don-t need to commit DDL.
oracle.execute('ddl_statements')
# Ensure that we always disconnect from the database to avoid
# ORA-00018: Maximum number of sessions exceeded.
finally:
oracle.disconnect()
Dalsza lektura:
cx_Oracle dokumentacja
Dlaczego nie używać wyjątków jako zwykłego przepływu kontroli?
Czy obsługa wyjątków Pythona jest bardziej wydajna niż PHP i/lub inne języki?
Argumenty za lub przeciw używaniu try catch jako operatorów logicznych