ฉันจะอ่านไฟล์ทีละบรรทัดใน Python ได้อย่างไร


137

ในสมัยก่อนประวัติศาสตร์ (Python 1.4) เราทำ:

fp = open('filename.txt')
while 1:
    line = fp.readline()
    if not line:
        break
    print line

หลังจาก Python 2.1 เราทำ:

for line in open('filename.txt').xreadlines():
    print line

ก่อนที่เราจะได้โปรโตคอล iterator ที่สะดวกใน Python 2.3 และสามารถทำได้:

for line in open('filename.txt'):
    print line

ฉันเห็นตัวอย่างโดยใช้ verbose มากขึ้น:

with open('filename.txt') as fp:
    for line in fp:
        print line

นี่เป็นวิธีที่ต้องการหรือไม่

[แก้ไข] ฉันได้รับพร้อมกับคำสั่งเพื่อให้แน่ใจว่าการปิดไฟล์ ... แต่ทำไมมันไม่รวมอยู่ในโปรโตคอล iterator สำหรับวัตถุไฟล์?


4
imho, ข้อเสนอแนะสุดท้ายคือไม่ verbose มากกว่าหนึ่งก่อน มันทำงานได้มากขึ้น (ช่วยให้มั่นใจว่าไฟล์จะถูกปิดเมื่อคุณทำเสร็จแล้ว)
azhrei

1
@azhrei เป็นอีกหนึ่งบรรทัดดังนั้นจึงเป็นเรื่องจริงมากขึ้น
thebjorn

7
ฉันได้สิ่งที่คุณพูด แต่ฉันแค่พูดเปรียบเทียบแอปเปิ้ลกับแอปเปิ้ลคำแนะนำสุดท้ายที่สองในโพสต์ของคุณต้องการรหัสการจัดการข้อยกเว้นและตรงกับสิ่งที่ตัวเลือกสุดท้ายทำ ดังนั้นในทางปฏิบัติมันมีความละเอียดมากขึ้น ฉันเดาว่ามันขึ้นอยู่กับบริบทว่าตัวเลือกใดในสองตัวเลือกสุดท้ายที่ดีที่สุดจริงๆ
azhrei

คำตอบ:


227

มีเหตุผลหนึ่งข้อว่าทำไมถึงต้องการสิ่งต่อไปนี้:

with open('filename.txt') as fp:
    for line in fp:
        print line

พวกเราทุกคนต่างก็ใจแตกโดยรูปแบบการนับการอ้างอิงที่ค่อนข้างแน่นอนของ CPython สำหรับการเก็บขยะ อื่น ๆ การใช้งานสมมุติของ Python จะไม่จำเป็นต้องปิดไฟล์ "เร็วพอ" โดยไม่มีwithบล็อกหากพวกเขาใช้รูปแบบอื่น ๆ เพื่อเรียกคืนหน่วยความจำ

ในการใช้งานดังกล่าวคุณอาจได้รับข้อผิดพลาด "เปิดไฟล์มากเกินไป" จากระบบปฏิบัติการหากรหัสของคุณเปิดไฟล์เร็วกว่าที่ผู้รวบรวมขยะเรียกการเรียกใช้ finalizers ในการจัดการไฟล์ orphaned วิธีแก้ปัญหาตามปกติคือเรียกใช้ GC ทันที แต่นี่เป็นการแฮ็คที่น่ารังเกียจและต้องทำโดยทุกฟังก์ชั่นที่อาจพบข้อผิดพลาดรวมถึงที่อยู่ในไลบรารี ช่างฝันร้ายอะไร

หรือคุณสามารถใช้withบล็อก

คำถามโบนัส

(หยุดอ่านเดี๋ยวนี้หากสนใจเฉพาะจุดประสงค์ของคำถาม)

เหตุใดจึงไม่รวมอยู่ในโปรโตคอลตัววนซ้ำสำหรับวัตถุไฟล์

นี่เป็นคำถามแบบอัตนัยเกี่ยวกับการออกแบบ API ดังนั้นฉันจึงมีคำตอบแบบอัตนัยในสองส่วน

ในระดับความรู้สึกผิดปกติเพราะมันทำให้โปรโตคอลตัววนซ้ำทำสองสิ่งแยกกัน - ทำซ้ำข้ามบรรทัดและปิดตัวจัดการไฟล์ - และบ่อยครั้งที่ความคิดที่ไม่ดีที่จะทำให้ฟังก์ชั่นดูเรียบง่ายนั้นทำได้สองอย่าง ในกรณีนี้มันรู้สึกไม่ดีโดยเฉพาะอย่างยิ่งเนื่องจากตัววนซ้ำเกี่ยวข้องกับวิธีการทำงานแบบกึ่งคุณค่าตามเนื้อหาของไฟล์ แต่การจัดการตัวจัดการไฟล์เป็นงานแยกต่างหากอย่างสมบูรณ์ การทำให้ทั้งคู่ล่องหนกลายเป็นการกระทำที่น่าแปลกใจสำหรับคนที่อ่านรหัสและทำให้ยากขึ้นที่จะให้เหตุผลเกี่ยวกับพฤติกรรมของโปรแกรม

ภาษาอื่น ๆ ได้ข้อสรุปเดียวกัน Haskell จีบสั้น ๆ กับสิ่งที่เรียกว่า "lazy IO" ซึ่งช่วยให้คุณสามารถวนซ้ำไฟล์และปิดมันโดยอัตโนมัติเมื่อคุณไปถึงจุดสิ้นสุดของสตรีม ผู้ใช้ส่วนใหญ่ได้ย้ายไปที่การจัดการทรัพยากรที่ชัดเจนมากขึ้นเช่น Conduit ซึ่งทำงานเหมือนwithบล็อกใน Python

ในระดับเทคนิคมีบางสิ่งที่คุณอาจต้องการทำกับตัวจัดการไฟล์ใน Python ซึ่งจะไม่ทำงานเช่นกันหากการวนซ้ำปิดตัวจัดการไฟล์ ตัวอย่างเช่นสมมติว่าฉันต้องวนซ้ำไฟล์สองครั้ง:

with open('filename.txt') as fp:
    for line in fp:
        ...
    fp.seek(0)
    for line in fp:
        ...

ในขณะที่นี่เป็นกรณีการใช้งานที่ไม่ธรรมดาลองพิจารณาข้อเท็จจริงที่ว่าฉันอาจเพิ่งเพิ่มโค้ดสามบรรทัดที่ด้านล่างลงในฐานรหัสที่มีอยู่ซึ่ง แต่เดิมมีสามบรรทัดแรกสุด หากการวนซ้ำปิดไฟล์ฉันจะไม่สามารถทำเช่นนั้นได้ ดังนั้นการแยกการวนซ้ำและการจัดการทรัพยากรทำให้การเขียนโค้ดเป็นโปรแกรม Python ที่ทำงานได้ง่ายขึ้น

การเรียบเรียงเป็นหนึ่งในคุณสมบัติการใช้งานที่สำคัญที่สุดของภาษาหรือ API


1
+1 เพราะมันอธิบาย "เมื่อ" ในความคิดเห็นของฉันเกี่ยวกับ op ;-)
azhrei

แม้จะมีการใช้งานแบบอื่น แต่ด้วยตัวจัดการจะทำให้คุณเกิดปัญหากับโปรแกรมที่เปิดไฟล์หลายร้อยไฟล์อย่างรวดเร็ว โปรแกรมส่วนใหญ่สามารถเข้าถึงได้โดยใช้การอ้างอิงไฟล์ห้อยที่ไม่มีปัญหา นอกเสียจากว่าคุณจะปิดการใช้งานในที่สุด GC ก็จะทำการเปิดและจัดการไฟล์ withทำให้คุณสบายใจแม้ว่ามันจะเป็นวิธีปฏิบัติที่ดีที่สุด
โกหก Ryan

1
@DietrichEpp: บางที "dangling file reference" ไม่ใช่คำที่ถูกต้องฉันหมายถึงการจัดการไฟล์ที่ไม่สามารถเข้าถึงได้อีกต่อไป แต่ยังไม่ปิด ไม่ว่าในกรณีใด GC จะปิดตัวจัดการไฟล์เมื่อรวบรวมวัตถุไฟล์ดังนั้นตราบใดที่คุณไม่มีการอ้างอิงเพิ่มเติมไปยังวัตถุไฟล์และคุณไม่ได้ปิดใช้งาน GC และคุณไม่ได้เปิดไฟล์จำนวนมากอย่างรวดเร็ว อย่างต่อเนื่องคุณไม่น่าจะได้รับ "เปิดไฟล์มากเกินไป" เนื่องจากไม่ได้ปิดไฟล์
โกหก Ryan

1
ใช่นั่นคือสิ่งที่ฉันหมายถึง "ถ้ารหัสของคุณเปิดไฟล์เร็วกว่าที่ผู้รวบรวมขยะเรียก finalizers ในการจัดการไฟล์ orphaned"
Dietrich Epp

1
เหตุผลที่ใหญ่กว่าที่จะใช้กับคือถ้าคุณไม่ปิดไฟล์ก็ไม่จำเป็นต้องเขียนทันที
พลวง

20

ใช่,

with open('filename.txt') as fp:
    for line in fp:
        print line

เป็นวิธีที่จะไป

มันไม่ได้ verbose มากขึ้น ปลอดภัยกว่า


5

หากคุณปิดการใช้งานโดยบรรทัดพิเศษคุณสามารถใช้ฟังก์ชั่น wrapper ดังนี้:

def with_iter(iterable):
    with iterable as iter:
        for item in iter:
            yield item

for line in with_iter(open('...')):
    ...

ใน Python 3.3 yield fromคำสั่งจะทำให้ข้อความสั้นลง:

def with_iter(iterable):
    with iterable as iter:
        yield from iter

2
เรียกใช้ฟังก์ชั่น xreadlines .. และใส่ไว้ในไฟล์ชื่อ xreadlines.py และเรากลับไปที่ Python 2.1 syntax :-)
thebjorn

@thebjorn: บางที แต่ตัวอย่าง Python 2.1 ที่คุณอ้างถึงนั้นไม่ปลอดภัยจากตัวจัดการไฟล์ที่ไม่ได้เปิดเผยในการใช้งานแบบอื่น การอ่านไฟล์ Python 2.1 ที่ปลอดภัยจากตัวจัดการไฟล์ที่ไม่เปิดเผยจะใช้เวลาอย่างน้อย 5 บรรทัด
โกหก Ryan

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.