python - try - cx_Oracle y manejo de excepciones-¿Buenas prácticas?
tipos de excepción en python (2)
Sin embargo, si no se puede conectar, entonces
db
no existirá más abajo, por lo que establezcodb = None
arriba. Sin embargo, ¿es esa buena práctica?
No, establecer db = None
no es la mejor práctica. Hay dos posibilidades, ya sea que la conexión a la base de datos funcione o no.
Conectarse a la base de datos no funciona:
Como la excepción
cursor = db.Cursor()
capturó y no se volvió a elevar, continuará hasta que llegue acursor = db.Cursor()
.db == None
, entonces, una excepción que se asemeja aTypeError: ''NoneType'' object has no attribute ''Cursor''
será levantado. Como la excepción generada cuando falla la conexión de la base de datos ya se ha detectado, el motivo de la falla está disfrazado.Personalmente, siempre emitiría una excepción de conexión a menos que vaya a intentarlo en breve. Cómo lo atrapes depende de ti; Si el error persiste, le envío un correo electrónico para que diga "ir y revisar la base de datos".
Conectarse a la base de datos funciona:
La variable
db
se asigna en sutry:... except
bloque. Si el método deconnect
funciona, entoncesdb
se reemplaza con el objeto de conexión.
De cualquier manera, el valor inicial de db
nunca se usa.
Sin embargo, he escuchado que usar el manejo de excepciones para el control de flujo como esto es una mala práctica.
A diferencia de otros idiomas, Python utiliza el manejo de excepciones para el control de flujo. Al final de mi respuesta, he vinculado varias preguntas sobre Stack Overflow y Programadores que hacen una pregunta similar. En cada ejemplo verás las palabras "pero en Python".
Eso no quiere decir que deba ir por la borda, pero Python generalmente usa el mantra EAFP , "Es más fácil pedir perdón que permiso". Los tres ejemplos más votados en ¿Cómo verifico si existe una variable? Son buenos ejemplos de cómo ambos pueden usar el control de flujo o no.
¿Son una buena idea las excepciones de anidación? ¿O hay una mejor manera de lidiar con excepciones dependientes / en cascada como esta?
No hay nada malo con las excepciones anidadas, una vez más, siempre y cuando lo hagas sanamente. Considera tu código. Podría eliminar todas las excepciones y envolver todo en un try:... except
bloque. Si se produce una excepción, entonces se sabe lo que era, pero es un poco más difícil averiguar exactamente qué fue lo que salió mal.
¿Qué sucede entonces si desea enviar un correo electrónico sobre la falla de cursor.execute
? Debe tener una excepción alrededor de cursor.execute
para realizar esta tarea. Luego vuelve a subir la excepción para que quede atrapado en tu try:...
externo try:...
No volver a elevar resultaría en que su código continúe como si no hubiera pasado nada y cualquier lógica que haya puesto en su try:...
externo try:...
para hacer frente a una excepción se ignoraría.
En última instancia, todas las excepciones se heredan de BaseException
.
Además, hay algunas partes (por ejemplo, fallas de conexión) en las que me gustaría que el script terminara, por lo tanto, la llamada de sys.exit () comentada.
He agregado una clase simple y cómo llamarlo, que es aproximadamente cómo haría lo que está tratando de hacer. Si esto se va a ejecutar en segundo plano, la impresión de los errores no vale la pena: la gente no estará sentada allí en busca de errores. Deben iniciar sesión independientemente de su forma estándar y de notificar a las personas correspondientes. He eliminado la impresión por este motivo y la he reemplazado con un recordatorio para iniciar sesión.
Como he dividido la clase en múltiples funciones cuando el método de connect
falla y se genera una excepción, la llamada de execute
no se ejecutará y la secuencia de comandos finalizará, después de intentar desconectarse.
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()
Entonces llámalo:
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()
Otras lecturas:
¿Por qué no usar excepciones como flujo regular de control?
¿La gestión de excepciones de Python es más eficiente que PHP y / u otros lenguajes?
Argumentos a favor o en contra de usar try catch como operadores lógicos
Estoy tratando de usar cx_Oracle para conectarse a una instancia de Oracle y ejecutar algunas sentencias DDL:
db = None
try:
db = cx_Oracle.connect(''username'', ''password'', ''hostname:port/SERVICENAME'')
#print(db.version)
except cx_Oracle.DatabaseError as e:
error, = e.args
if error.code == 1017:
print(''Please check your credentials.'')
# sys.exit()?
else:
print(''Database connection error: %s''.format(e))
cursor = db.cursor()
try:
cursor.execute(ddl_statements)
except cx_Oracle.DatabaseError as e:
error, = e.args
if error.code == 955:
print(''Table already exists'')
if error.code == 1031:
print("Insufficient privileges - are you sure you''re using the owner account?")
print(error.code)
print(error.message)
print(error.context)
cursor.close()
db.commit()
db.close()
Sin embargo, no estoy seguro de cuál es el mejor diseño para el manejo de excepciones aquí.
En primer lugar, creo el objeto db
dentro de un bloque try, para detectar cualquier error de conexión.
Sin embargo, si no se puede conectar, entonces db
no existirá más abajo, por lo que establezco db = None
arriba. Sin embargo, ¿es esa buena práctica?
Idealmente, necesito detectar errores al conectar, luego errores al ejecutar las declaraciones DDL, y así sucesivamente.
¿Son una buena idea las excepciones de anidación? ¿O hay una mejor manera de lidiar con excepciones dependientes / en cascada como esta?
Además, hay algunas partes (por ejemplo, fallas de conexión) en las que me gustaría que el script terminara, por lo tanto, la llamada de sys.exit()
comentada. Sin embargo, he escuchado que usar el manejo de excepciones para el control de flujo como esto es una mala práctica. ¿Pensamientos?
Una solución diferente y posiblemente elegante es utilizar un decorador para las funciones de llamada a la base de datos. El decorador permite la capacidad de corregir el error y volver a intentar la llamada a la base de datos. Para las conexiones obsoletas, la solución es volver a conectar y volver a emitir la llamada. Aquí está el decorador que trabajó para mí:
####### Decorator named dbReconnect ########
#Retry decorator
#Retries a database function twice when the 1st fails on a stale connection
def dbReconnect():
def real_decorator(function):
def wrapper(*args, **kwargs):
try:
return function(*args, **kwargs)
except Exception as inst:
print ("DB error({0}):".format(inst))
print ("Reconnecting")
#...Code for reconnection is to be placed here..
......
#..end of code for reconnection
return function(*args, **kwargs)
return wrapper
return real_decorator
###### Decorate the DB Call like this: #####
@dbReconnect()
def DB_FcnCall(...):
....
Más detalles sobre Github: https://github.com/vvaradarajan/DecoratorForDBReconnect/wiki
Nota: Si usa grupos de conexiones, las técnicas internas de grupos de conexiones que verifican una conexión y la actualizan si están obsoletas, también resolverán el problema.