แทนที่จะถามว่าแนวปฏิบัติมาตรฐานคืออะไรเนื่องจากมักไม่ชัดเจนและเป็นเรื่องส่วนตัวคุณอาจลองหาคำแนะนำจากโมดูลนั้น โดยทั่วไปการใช้with
คำหลักตามที่ผู้ใช้รายอื่นแนะนำเป็นความคิดที่ดี แต่ในสถานการณ์เฉพาะนี้อาจไม่ได้ให้ฟังก์ชันที่คุณคาดหวัง
ในเวอร์ชัน 1.2.5 ของโมดูลMySQLdb.Connection
ใช้โปรโตคอลตัวจัดการบริบทด้วยรหัสต่อไปนี้ ( 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()
มีคำถามและคำตอบที่มีwith
อยู่มากมายเกี่ยวกับอยู่แล้วหรือคุณสามารถอ่านทำความเข้าใจกับคำสั่ง "ด้วย" ของ Pythonแต่โดยพื้นฐานแล้วสิ่งที่เกิดขึ้นคือ__enter__
ดำเนินการเมื่อเริ่มต้นwith
บล็อกและ__exit__
ดำเนินการเมื่อออกจากwith
บล็อก คุณสามารถใช้ไวยากรณ์ที่เป็นทางเลือกwith EXPR as VAR
เพื่อผูกอ็อบเจ็กต์ที่ส่งคืนด้วย __enter__
ชื่อหากคุณต้องการอ้างอิงอ็อบเจ็กต์นั้นในภายหลัง ดังนั้นจากการใช้งานข้างต้นนี่เป็นวิธีง่ายๆในการสืบค้นฐานข้อมูลของคุณ:
connection = MySQLdb.connect(...)
with connection as cursor:
cursor.execute('select 1;')
result = cursor.fetchall()
print result
คำถามคือสถานะของการเชื่อมต่อและเคอร์เซอร์หลังจากออกจากwith
บล็อกคืออะไร? __exit__
วิธีการแสดงข้างต้นสายเท่านั้นself.rollback()
หรือself.commit()
และไม่วิธีการเหล่านั้นไปในการที่จะเรียกclose()
วิธีการ เคอร์เซอร์เองไม่ได้__exit__
กำหนดวิธีการไว้ - และจะไม่สำคัญว่าจะเป็นอย่างไรเพราะwith
เป็นเพียงการจัดการการเชื่อมต่อเท่านั้น ดังนั้นทั้งการเชื่อมต่อและเคอร์เซอร์จะยังคงเปิดอยู่หลังจากออกจากwith
บล็อก สิ่งนี้ยืนยันได้อย่างง่ายดายโดยการเพิ่มรหัสต่อไปนี้ในตัวอย่างด้านบน:
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'
คุณควรเห็นผลลัพธ์ "เคอร์เซอร์เปิดอยู่; การเชื่อมต่อเปิดอยู่" พิมพ์เป็น stdout
ฉันเชื่อว่าคุณต้องปิดเคอร์เซอร์ก่อนที่จะทำการเชื่อมต่อ
ทำไม? MySQL C APIซึ่งเป็นพื้นฐานสำหรับการMySQLdb
ไม่ใช้วัตถุเคอร์เซอร์ใด ๆ ตามที่ระบุไว้ในเอกสารโมดูล: "MySQL ไม่สนับสนุนเคอร์เซอร์อย่างไรก็ตามเคอร์เซอร์เป็นเทิดทูนอย่างง่ายดาย." อันที่จริงMySQLdb.cursors.BaseCursor
คลาสนี้สืบทอดมาโดยตรงobject
และไม่มีข้อ จำกัด ดังกล่าวสำหรับเคอร์เซอร์ที่เกี่ยวข้องกับการกระทำ / ย้อนกลับ นักพัฒนา Oracle กล่าวว่า :
cnx.commit () ก่อน cur.close () ฟังดูสมเหตุสมผลที่สุดสำหรับฉัน บางทีคุณอาจทำตามกฎ: "ปิดเคอร์เซอร์ถ้าคุณไม่ต้องการอีกต่อไป" ดังนั้นกระทำ () ก่อนปิดเคอร์เซอร์ ในท้ายที่สุดสำหรับ Connector / Python ก็ไม่ได้สร้างความแตกต่างมากนัก แต่หรือฐานข้อมูลอื่น ๆ อาจ
ฉันคาดหวังว่าจะใกล้เคียงที่สุดเท่าที่คุณจะเข้าสู่ "แนวปฏิบัติมาตรฐาน" ในเรื่องนี้
มีข้อได้เปรียบที่สำคัญในการค้นหาชุดธุรกรรมที่ไม่ต้องใช้การคอมมิชชันกลางเพื่อที่คุณจะได้ไม่ต้องรับเคอร์เซอร์ใหม่สำหรับแต่ละธุรกรรมหรือไม่?
ฉันสงสัยมากและในการพยายามทำเช่นนั้นคุณอาจแนะนำข้อผิดพลาดของมนุษย์เพิ่มเติม ดีกว่าที่จะตัดสินใจเกี่ยวกับอนุสัญญาและยึดติดกับมัน
มีค่าใช้จ่ายจำนวนมากสำหรับการรับเคอร์เซอร์ใหม่หรือไม่ใช่เรื่องใหญ่?
ค่าโสหุ้ยมีค่าเล็กน้อยและไม่ได้สัมผัสกับเซิร์ฟเวอร์ฐานข้อมูลเลย ทั้งหมดอยู่ในการใช้งาน MySQLdb คุณสามารถดูBaseCursor.__init__
ใน github ได้หากคุณอยากรู้จริงๆว่าเกิดอะไรขึ้นเมื่อคุณสร้างเคอร์เซอร์ใหม่
ย้อนกลับไปก่อนหน้านี้ตอนที่เรากำลังคุยกันwith
บางทีตอนนี้คุณสามารถเข้าใจได้แล้วว่าทำไมMySQLdb.Connection
คลาส__enter__
และ__exit__
เมธอดจึงทำให้คุณมีเคอร์เซอร์ออบเจ็กต์ใหม่ในทุกๆwith
บล็อกและไม่ต้องกังวลกับการติดตามหรือปิดมันในตอนท้ายของบล็อก มีน้ำหนักเบาพอสมควรและมีไว้เพื่อความสะดวกของคุณเท่านั้น
หากคุณจำเป็นต้องจัดการกับวัตถุเคอร์เซอร์แบบไมโครคุณสามารถใช้contextlib.closingเพื่อชดเชยความจริงที่ว่าวัตถุเคอร์เซอร์ไม่มี__exit__
วิธีการที่กำหนดไว้ สำหรับเรื่องนั้นคุณยังสามารถใช้เพื่อบังคับให้วัตถุการเชื่อมต่อปิดตัวเองเมื่อออกจากwith
บล็อก สิ่งนี้ควรแสดงผล "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'
โปรดทราบว่า with closing(arg_obj)
จะไม่เรียกวัตถุอาร์กิวเมนต์__enter__
และ__exit__
วิธีการ มันจะเพียงโทรวัตถุอาร์กิวเมนต์ของclose
วิธีการในตอนท้ายของwith
บล็อก (หากต้องการเห็นนี้ในการดำเนินการเพียงแค่กำหนดชั้นเรียนFoo
ด้วย__enter__
, __exit__
และclose
วิธีการที่มีง่ายprint
งบและเปรียบเทียบสิ่งที่เกิดขึ้นเมื่อคุณทำwith Foo(): pass
กับสิ่งที่เกิดขึ้นเมื่อคุณทำwith closing(Foo()): pass
.) นี้มีสองผลกระทบอย่างมีนัยสำคัญ:
ขั้นแรกหากเปิดใช้งานโหมด autocommit MySQLdb จะBEGIN
ทำธุรกรรมอย่างชัดเจนบนเซิร์ฟเวอร์เมื่อคุณใช้with connection
และกระทำหรือย้อนกลับธุรกรรมที่ส่วนท้ายของบล็อก สิ่งเหล่านี้เป็นพฤติกรรมเริ่มต้นของ MySQLdb ซึ่งมีไว้เพื่อปกป้องคุณจากพฤติกรรมเริ่มต้นของ MySQL ในการส่งคำสั่ง DML ใด ๆ และทั้งหมดทันที MySQLdb จะถือว่าเมื่อคุณใช้ตัวจัดการบริบทคุณต้องการธุรกรรมและใช้ Explicit BEGIN
เพื่อข้ามการตั้งค่า autocommit บนเซิร์ฟเวอร์ หากคุณคุ้นเคยกับการใช้with connection
งานคุณอาจคิดว่า autocommit ถูกปิดใช้งานเมื่อมีการข้ามเท่านั้น คุณอาจได้รับความประหลาดใจที่ไม่พึงประสงค์ถ้าคุณเพิ่มclosing
รหัสของคุณและสูญเสียความสมบูรณ์ของธุรกรรม คุณจะไม่สามารถย้อนกลับการเปลี่ยนแปลงได้คุณอาจเริ่มเห็นจุดบกพร่องที่เกิดขึ้นพร้อมกันและอาจไม่ชัดเจนในทันทีว่าทำไม
ประการที่สองwith closing(MySQLdb.connect(user, pass)) as VAR
ผูกวัตถุการเชื่อมต่อไปVAR
ในทางตรงกันข้ามกับwith MySQLdb.connect(user, pass) as VAR
ที่ผูกวัตถุเคอร์เซอร์ใหม่VAR
เพื่อ ในกรณีหลังนี้คุณจะไม่สามารถเข้าถึงวัตถุเชื่อมต่อได้โดยตรง! แต่คุณจะต้องใช้connection
แอตทริบิวต์ของเคอร์เซอร์ซึ่งให้การเข้าถึงพร็อกซีไปยังการเชื่อมต่อเดิม เมื่อเคอร์เซอร์จะปิดมันแอตทริบิวต์มีการตั้งค่าconnection
None
สิ่งนี้ส่งผลให้เกิดการเชื่อมต่อที่ถูกละทิ้งซึ่งจะค้างอยู่จนกว่าจะเกิดเหตุการณ์อย่างใดอย่างหนึ่งต่อไปนี้:
- การอ้างอิงเคอร์เซอร์ทั้งหมดจะถูกลบออก
- เคอร์เซอร์อยู่นอกขอบเขต
- การเชื่อมต่อหมดเวลา
- การเชื่อมต่อถูกปิดด้วยตนเองผ่านเครื่องมือการดูแลระบบเซิร์ฟเวอร์
คุณสามารถทดสอบสิ่งนี้ได้โดยตรวจสอบการเชื่อมต่อแบบเปิด (ใน Workbench หรือโดยใช้SHOW PROCESSLIST
) ในขณะที่ดำเนินการบรรทัดต่อไปนี้ทีละบรรทัด:
with MySQLdb.connect(...) as my_curs:
pass
my_curs.close()
my_curs.connection
my_curs.connection.close()
del my_curs