Au lieu de demander quelle est la pratique standard, car elle est souvent peu claire et subjective, vous pouvez essayer de consulter le module lui-même pour obtenir des conseils. En général, utiliser le with
mot - clé comme un autre utilisateur l'a suggéré est une excellente idée, mais dans ce cas précis, il peut ne pas vous donner tout à fait les fonctionnalités que vous attendez.
Depuis la version 1.2.5 du module, MySQLdb.Connection
implémente le protocole du 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 déjà plusieurs questions / réponses with
, ou vous pouvez lire l'instruction «with» de Python , mais ce qui se passe essentiellement, c'est qu'elle __enter__
s'exécute au début du with
bloc 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 prévoyez de référencer cet objet ultérieurement. Donc, étant donné l'implémentation ci-dessus, voici un moyen simple d'interroger votre base de données:
connection = MySQLdb.connect(...)
with connection as cursor:
cursor.execute('select 1;')
result = cursor.fetchall()
print result
La question est maintenant, quels sont les états de la connexion et du curseur après avoir quitté le with
bloc? La __exit__
méthode présentée ci-dessus appelle uniquement self.rollback()
ou self.commit()
, et aucune de ces méthodes n'appelle la close()
méthode. Le curseur lui-même n'a pas de __exit__
méthode définie - et n'aurait pas d'importance s'il le faisait, car il with
ne gère que la connexion. Par conséquent, la connexion et le curseur restent ouverts après la sortie du 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' API C MySQL , qui en est la base MySQLdb
, n'implémente aucun objet curseur, comme le suggère la documentation du module: "MySQL ne prend pas en charge les curseurs; cependant, les curseurs sont facilement émulés." En effet, la MySQLdb.cursors.BaseCursor
classe hérite directement des object
curseurs et n'impose aucune restriction de ce type en ce qui concerne la validation / la restauration. 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." Ainsi commit () avant de fermer le curseur. En fin de compte, pour Connector / Python, cela ne fait pas beaucoup de différence, mais ou pour d'autres bases de données, cela pourrait.
Je pense que c'est aussi proche que vous allez arriver à 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 beaucoup, 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 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.
En revenant à plus tôt lorsque nous discutions with
, vous pouvez peut-être maintenant comprendre pourquoi la MySQLdb.Connection
classe __enter__
et les __exit__
méthodes vous donnent un tout nouvel objet curseur dans chaque with
bloc et ne prennent pas la peine de le suivre ou de le fermer à la fin du bloc. Il est assez léger et existe uniquement pour votre commodité.
S'il est vraiment important pour vous de microgérer l'objet curseur, vous pouvez utiliser contextlib.closing pour compenser le fait que l'objet curseur n'a pas de __exit__
méthode définie . Pour cette question, vous pouvez également l'utiliser pour forcer l'objet de connexion à se fermer à la sortie d'un with
bloc. Cela devrait afficher "my_curs est fermé; my_conn est fermé":
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)
cela n'appellera pas les méthodes __enter__
et de l'objet argument __exit__
; il n'appel de l'objet de l' argument de la méthode à la fin du bloc. (Pour voir en action, définir simplement une classe avec , et des méthodes simples contenant des déclarations, et comparer ce qui se passe quand vous faites à ce qui se passe quand vous faites .) Cela a deux conséquences importantes:close
with
Foo
__enter__
__exit__
close
print
with Foo(): pass
with closing(Foo()): pass
Premièrement, si le mode autocommit est activé, MySQLdb effectuera BEGIN
une transaction explicite sur le serveur lorsque vous utiliserez with connection
et validerez ou annulerez 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 souhaitez une transaction et utilise l'explicite BEGIN
pour contourner le paramètre de validation automatique sur le serveur. Si vous avez l'habitude d'utiliser with connection
, vous pourriez penser que l'autocommit est désactivé alors qu'en réalité il était seulement contourné. Vous pourriez avoir une mauvaise surprise si vous ajoutezclosing
à votre code et perdez l'intégrité transactionnelle; vous ne pourrez pas annuler les modifications, vous pouvez commencer à voir des bogues de concurrence et pourquoi ne pas être immédiatement évident.
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 l' connection
attribut du curseur , qui fournit un accès proxy à la connexion d'origine. Lorsque le curseur est fermé, son connection
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 en utilisantSHOW PROCESSLIST
) tout en exécutant les lignes suivantes une par une:
with MySQLdb.connect(...) as my_curs:
pass
my_curs.close()
my_curs.connection
my_curs.connection.close()
del my_curs