Au lieu de demander quelle est la pratique courante, puisque c'est souvent peu clair et subjectif, vous pouvez essayer de consulter le module lui-même pour obtenir des conseils. En général, en utilisant le with
comme un autre utilisateur l'a suggéré est une excellente idée, mais dans ce cas précis, il se peut qu'il ne vous offre pas tout à fait les fonctionnalités que vous attendez.
Depuis la version 1.2.5 du module, MySQLdb.Connection
implémente le protocole de gestionnaire de contexte
avec le code suivant (github
):
def __enter__(self):
if self.get_autocommit():
self.query("BEGIN")
return self.cursor()
def __exit__(self, exc, value, tb):
if exc:
self.rollback()
else:
self.commit()
Il existe plusieurs questions/réponses existantes sur with
déjà, ou vous pouvez lire Comprendre l'instruction "with" de Python
, mais essentiellement ce qui se passe est que __enter__
s'exécute au début du with
bloquer et __exit__
s'exécute en quittant le with
bloc. Vous pouvez utiliser la syntaxe facultative with EXPR as VAR
pour lier l'objet renvoyé par __enter__
à un nom si vous avez l'intention de référencer cet objet ultérieurement. Ainsi, compte tenu de l'implémentation ci-dessus, voici un moyen simple d'interroger votre base de données :
connection = MySQLdb.connect(...)
with connection as cursor: # connection.__enter__ executes at this line
cursor.execute('select 1;')
result = cursor.fetchall() # connection.__exit__ executes after this line
print result # prints "((1L,),)"
La question est maintenant, quels sont les états de la connexion et du curseur après avoir quitté le with
bloc? La __exit__
la méthode ci-dessus appelle uniquement self.rollback()
ou self.commit()
, et aucune de ces méthodes n'appelle le close()
méthode. Le curseur lui-même n'a pas de __exit__
méthode définie - et cela n'aurait pas d'importance si c'était le cas, car with
gère uniquement la connexion. Par conséquent, la connexion et le curseur restent ouverts après avoir quitté le with
bloc. Ceci est facilement confirmé en ajoutant le code suivant à l'exemple ci-dessus :
try:
cursor.execute('select 1;')
print 'cursor is open;',
except MySQLdb.ProgrammingError:
print 'cursor is closed;',
if connection.open:
print 'connection is open'
else:
print 'connection is closed'
Vous devriez voir la sortie "le curseur est ouvert ; la connexion est ouverte" imprimée sur stdout.
Je pense que vous devez fermer le curseur avant de valider la connexion.
Pourquoi? L'l'API MySQL C
, qui est la base de MySQLdb
, n'implémente aucun objet curseur, comme indiqué dans la documentation du module :"MySQL ne prend pas en charge les curseurs ; cependant, les curseurs sont facilement émulés."
En effet, le MySQLdb.cursors.BaseCursor
la classe hérite directement de object
et n'impose aucune restriction de ce type sur les curseurs en ce qui concerne la validation/annulation. Un développeur Oracle avait ceci à dire
:
cnx.commit() avant cur.close() me semble le plus logique. Peut-être pouvez-vous suivre la règle :"Fermez le curseur si vous n'en avez plus besoin." Donc commit() avant de fermer le curseur. Au final, pourConnector/Python, cela ne fait pas beaucoup de différence, mais ou d'autres bases de données cela pourrait.
Je pense que c'est aussi proche que possible de la "pratique standard" sur ce sujet.
Y a-t-il un avantage significatif à trouver des ensembles de transactions qui ne nécessitent pas de validations intermédiaires afin que vous n'ayez pas à obtenir de nouveaux curseurs pour chaque transaction ?
J'en doute fort, et en essayant de le faire, vous risquez d'introduire une erreur humaine supplémentaire. Mieux vaut décider d'une convention et s'y tenir.
Y a-t-il beaucoup de frais généraux pour obtenir de nouveaux curseurs, ou n'est-ce pas un gros problème ?
La surcharge est négligeable et ne touche pas du tout le serveur de base de données; c'est entièrement dans l'implémentation de MySQLdb. Vous pouvez regarder BaseCursor.__init__
sur github
si vous êtes vraiment curieux de savoir ce qui se passe lorsque vous créez un nouveau curseur.
Revenons à plus tôt lorsque nous parlions de with
, peut-être comprenez-vous maintenant pourquoi le MySQLdb.Connection
classe __enter__
et __exit__
les méthodes vous donnent un tout nouvel objet curseur dans chaque with
bloc et ne vous embêtez pas à en garder une trace ou à le fermer à la fin du bloc. Il est assez léger et n'existe que pour votre commodité.
S'il est vraiment important pour vous de microgérer l'objet curseur, vous pouvez utiliser contextlib.fermeture
pour compenser le fait que l'objet curseur n'a pas de __exit__
défini méthode. D'ailleurs, vous pouvez également l'utiliser pour forcer l'objet de connexion à se fermer à la sortie d'un with
bloc. Cela devrait afficher "my_curs is closed ; my_conn is closed":
from contextlib import closing
import MySQLdb
with closing(MySQLdb.connect(...)) as my_conn:
with closing(my_conn.cursor()) as my_curs:
my_curs.execute('select 1;')
result = my_curs.fetchall()
try:
my_curs.execute('select 1;')
print 'my_curs is open;',
except MySQLdb.ProgrammingError:
print 'my_curs is closed;',
if my_conn.open:
print 'my_conn is open'
else:
print 'my_conn is closed'
Notez que with closing(arg_obj)
n'appellera pas le __enter__
de l'objet argument et __exit__
méthodes; ce sera seulement appeler le close
de l'objet argument méthode à la fin du with
bloc. (Pour voir cela en action, définissez simplement une classe Foo
avec __enter__
, __exit__
, et close
méthodes contenant de simples print
et comparez ce qui se passe lorsque vous faites with Foo(): pass
à ce qui se passe quand vous faites with closing(Foo()): pass
.) Cela a deux implications importantes :
Tout d'abord, si le mode de validation automatique est activé, MySQLdb BEGIN
une transaction explicite sur le serveur lorsque vous utilisez with connection
et valider ou annuler la transaction à la fin du bloc. Ce sont des comportements par défaut de MySQLdb, destinés à vous protéger du comportement par défaut de MySQL consistant à valider immédiatement toutes les instructions DML. MySQLdb suppose que lorsque vous utilisez un gestionnaire de contexte, vous voulez une transaction, et utilise le BEGIN
explicite pour contourner le paramètre de validation automatique sur le serveur. Si vous avez l'habitude d'utiliser with connection
, vous pourriez penser que la validation automatique est désactivée alors qu'en réalité elle était simplement contournée. Vous pourriez avoir une mauvaise surprise si vous ajoutez closing
à votre code et perdre l'intégrité transactionnelle ; vous ne pourrez pas annuler les modifications, vous commencerez peut-être à voir des bogues de simultanéité et la raison n'en sera peut-être pas immédiatement évidente.
Deuxièmement, with closing(MySQLdb.connect(user, pass)) as VAR
lie l'objet de connexion à VAR
, contrairement à with MySQLdb.connect(user, pass) as VAR
, qui lie un nouvel objet curseur à VAR
. Dans ce dernier cas vous n'auriez pas d'accès direct à l'objet de connexion ! Au lieu de cela, vous devrez utiliser la connection
du curseur , qui fournit un accès proxy à la connexion d'origine. Lorsque le curseur est fermé, sa connection
l'attribut est défini sur None
. Cela entraîne une connexion abandonnée qui restera jusqu'à ce que l'un des événements suivants se produise :
- Toutes les références au curseur sont supprimées
- Le curseur sort de la portée
- La connexion expire
- La connexion est fermée manuellement via les outils d'administration du serveur
Vous pouvez tester cela en surveillant les connexions ouvertes (dans Workbench ou par en utilisant SHOW PROCESSLIST
) en exécutant une à une les lignes suivantes :
with MySQLdb.connect(...) as my_curs:
pass
my_curs.close()
my_curs.connection # None
my_curs.connection.close() # throws AttributeError, but connection still open
del my_curs # connection will close here