Namísto toho, abyste se ptali, co je standardní postup, protože to je často nejasné a subjektivní, můžete zkusit hledat pokyny v samotném modulu. Obecně platí, že pomocí with
klíčové slovo, jak navrhl jiný uživatel, je skvělý nápad, ale za těchto konkrétních okolností vám nemusí poskytnout takovou funkčnost, jakou očekáváte.
Od verze 1.2.5 modulu MySQLdb.Connection
implementuje protokol správce kontextu
s následujícím kódem (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()
Existuje několik existujících otázek a odpovědí o with
již nebo si můžete přečíst Porozumění příkazu Python "with"
, ale v podstatě se stane to, že __enter__
provede se na začátku with
blok a __exit__
provede se po opuštění with
blok. Můžete použít volitelnou syntaxi with EXPR as VAR
svázat objekt vrácený __enter__
na jméno, chcete-li později na tento objekt odkazovat. Vzhledem k výše uvedené implementaci je zde jednoduchý způsob dotazování databáze:
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,),)"
Otázkou nyní je, jaké jsou stavy připojení a kurzoru po opuštění with
blok? __exit__
výše uvedená metoda volá pouze self.rollback()
nebo self.commit()
a žádná z těchto metod nepokračuje ve volání close()
metoda. Samotný kurzor nemá žádný __exit__
metoda definovaná – a na tom by nezáleželo, protože with
pouze spravuje připojení. Po opuštění with
tedy spojení i kurzor zůstanou otevřené blok. To lze snadno potvrdit přidáním následujícího kódu do výše uvedeného příkladu:
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'
Měli byste vidět výstup „kurzor je otevřený; připojení je otevřené“ vytištěné na stdout.
Domnívám se, že před provedením připojení musíte zavřít kurzor.
Proč? MySQL C API
, což je základ pro MySQLdb
, neimplementuje žádný objekt kurzoru, jak vyplývá z dokumentace modulu:"MySQL nepodporuje kurzory, ale kurzory lze snadno emulovat."
Opravdu, MySQLdb.cursors.BaseCursor
třída dědí přímo z object
a neukládá žádná taková omezení na kurzory s ohledem na potvrzení/vrácení zpět. Vývojář Oracle to řekl
:
cnx.commit() před cur.close() mi přijde nejlogičtější. Možná se můžete řídit pravidlem:"Zavřete kurzor, pokud jej již nepotřebujete." Před zavřením kurzoru tedy commit(). V případě Connectoru/Pythonu to nakonec příliš nemění, ale v jiných databázích ano.
Očekávám, že je to tak blízko, jak se dostanete ke „standardní praxi“ na toto téma.
Existuje nějaká významná výhoda při hledání sad transakcí, které nevyžadují přechodné potvrzení, takže pro každou transakci nemusíte získávat nové kurzory?
Velmi o tom pochybuji a při pokusu o to můžete způsobit další lidskou chybu. Je lepší rozhodnout se pro konvenci a držet se jí.
Je na získávání nových kurzorů spousta režie, nebo to prostě není velký problém?
Režie je zanedbatelná a vůbec se nedotýká databázového serveru; je to zcela v rámci implementace MySQLdb. Můžete se podívat na BaseCursor.__init__
na github
pokud jste opravdu zvědaví, co se děje, když vytvoříte nový kurzor.
Vraťme se k dřívější době, kdy jsme diskutovali with
, možná nyní chápete, proč MySQLdb.Connection
třídy __enter__
a __exit__
metody vám poskytnou zcela nový objekt kurzoru v každém with
blok a neobtěžujte se jeho sledováním nebo zavíráním na konci bloku. Je poměrně lehký a existuje pouze pro vaše pohodlí.
Pokud je pro vás opravdu tak důležité mikrospravovat objekt kurzoru, můžete použít contextlib.closing
aby se vynahradil fakt, že objekt kurzoru nemá definovaný __exit__
metoda. V tomto případě jej můžete také použít k vynucení toho, aby se objekt spojení uzavřel při ukončení with
blok. To by mělo vypsat "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'
Všimněte si, že with closing(arg_obj)
nebude volat objekt argumentu __enter__
a __exit__
metody; bude pouze zavolejte objekt argumentu close
metoda na konci with
blok. (Chcete-li to vidět v akci, jednoduše definujte třídu Foo
pomocí __enter__
, __exit__
a close
metody obsahující jednoduchý print
a porovnejte, co se stane, když provedete with Foo(): pass
k tomu, co se stane, když provedete with closing(Foo()): pass
.) To má dva významné důsledky:
Za prvé, pokud je povolen režim automatického potvrzení, MySQLdb BEGIN
explicitní transakce na serveru, když použijete with connection
a potvrzení nebo vrácení transakce na konci bloku. Toto jsou výchozí chování MySQLdb, která vás mají chránit před výchozím chováním MySQL, kterým je okamžité provedení všech příkazů DML. MySQLdb předpokládá, že když používáte správce kontextu, chcete transakci, a používá explicitní BEGIN
obejít nastavení automatického potvrzení na serveru. Pokud jste zvyklí používat with connection
, můžete si myslet, že automatické potvrzení je zakázáno, i když ve skutečnosti bylo pouze obejito. Pokud přidáte closing
, můžete být nepříjemně překvapeni k vašemu kódu a ztratíte transakční integritu; nebudete moci vrátit změny, můžete začít vidět chyby souběžnosti a nemusí být hned zřejmé proč.
Za druhé, with closing(MySQLdb.connect(user, pass)) as VAR
sváže objekt připojení na VAR
, na rozdíl od with MySQLdb.connect(user, pass) as VAR
, který sváže nový objekt kurzoru na VAR
. V druhém případě byste neměli přímý přístup k objektu připojení! Místo toho byste museli použít connection
kurzoru atribut, který poskytuje proxy přístup k původnímu připojení. Když je kurzor zavřený, jeho connection
atribut je nastaven na None
. Výsledkem je opuštěné připojení, které bude přetrvávat, dokud nenastane jedna z následujících situací:
- Všechny odkazy na kurzor jsou odstraněny
- Kurzor je mimo rozsah
- Vypršel časový limit připojení
- Připojení je ukončeno ručně pomocí nástrojů pro správu serveru
Můžete to otestovat sledováním otevřených připojení (ve Workbench nebo pomocí pomocí SHOW PROCESSLIST
) při provádění následujících řádků jeden po druhém:
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