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
db
jest przypisany w twoimtry:... except
blok. Jeśliconnect
metoda działa wtedydb
zostanie 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