การอ่านไฟล์ทั้งหมดเปิดให้ตัวจัดการไฟล์เปิดค้างไว้หรือไม่?


372

หากคุณอ่านไฟล์ทั้งหมดด้วยcontent = open('Path/to/file', 'r').read()จะมีการจัดการไฟล์เปิดค้างไว้จนกว่าสคริปต์จะออก? มีวิธีรัดกุมกว่าในการอ่านไฟล์ทั้งหมดหรือไม่?

คำตอบ:


585

คำตอบสำหรับคำถามนั้นขึ้นอยู่กับการใช้งานของ Python

เพื่อให้เข้าใจสิ่งที่เป็นข้อมูลเกี่ยวกับสิ่งนี้ให้ความสนใจเป็นพิเศษกับfileวัตถุที่เกิดขึ้นจริง ในรหัสของคุณวัตถุนั้นถูกกล่าวถึงเพียงครั้งเดียวในการแสดงออกและจะไม่สามารถเข้าถึงได้ทันทีหลังจากที่read()โทรกลับ

ซึ่งหมายความว่าวัตถุไฟล์เป็นขยะ คำถามเดียวที่เหลืออยู่คือ "เมื่อใดที่ตัวรวบรวมข้อมูลขยะจะรวบรวมวัตถุไฟล์?"

ใน CPython ซึ่งใช้ตัวนับอ้างอิงจะสังเกตเห็นขยะชนิดนี้ทันทีและจะถูกรวบรวมทันที นี่ไม่ใช่ความจริงของการใช้งานของงูหลามอื่น ๆ

ทางออกที่ดีกว่าเพื่อให้แน่ใจว่าไฟล์ถูกปิดเป็นรูปแบบนี้:

with open('Path/to/file', 'r') as content_file:
    content = content_file.read()

ซึ่งจะปิดไฟล์ทันทีหลังจากบล็อกสิ้นสุด แม้ว่าจะมีข้อยกเว้นเกิดขึ้น

แก้ไข: หากต้องการวางจุดปลีกย่อยบน:

อื่น ๆ กว่าfile.__exit__()ซึ่งก็คือ "อัตโนมัติ" เรียกว่าในwithการตั้งค่าผู้จัดการบริบทวิธีเดียวที่อื่น ๆ ที่file.close()มีการเรียกโดยอัตโนมัติ (นั่นคืออื่น ๆ กว่าอย่างชัดเจนเรียกมันด้วยตัวคุณเอง) file.__del__()ผ่านทาง สิ่งนี้ทำให้เรามีคำถาม__del__()ว่าจะได้รับการเรียกเมื่อใด

โปรแกรมที่เขียนอย่างถูกต้องไม่สามารถสรุปได้ว่า finalizers จะทำงานที่จุดใด ๆ ก่อนที่จะมีการยกเลิกโปรแกรม

- https://devblogs.microsoft.com/oldnewthing/20100809-00/?p=13203

โดยเฉพาะอย่างยิ่ง:

วัตถุจะไม่ถูกทำลายอย่างชัดเจน อย่างไรก็ตามเมื่อพวกเขาไม่สามารถเข้าถึงพวกเขาอาจถูกเก็บขยะ อนุญาตให้มีการนำไปใช้งานเพื่อเลื่อนการรวบรวมขยะหรือละเว้นไปพร้อมกัน - มันเป็นเรื่องของคุณภาพการใช้งานว่าการเก็บรวบรวมขยะถูกนำไปใช้อย่างไรตราบใดที่ไม่มีการรวบรวมวัตถุที่ยังสามารถเข้าถึงได้

[ ... ]

ปัจจุบัน CPython ใช้รูปแบบการนับการอ้างอิงด้วยการตรวจจับแบบล่าช้า (เป็นทางเลือก) ของขยะที่เชื่อมโยงแบบวนรอบซึ่งรวบรวมวัตถุส่วนใหญ่ทันทีที่เข้าไม่ถึง แต่ไม่รับประกันว่าจะรวบรวมขยะที่มีการอ้างอิงแบบวงกลม

- https://docs.python.org/3.5/reference/datamodel.html#objects-values-and-types

(เน้นเหมือง)

แต่ตามที่แนะนำการใช้งานอื่นอาจมีพฤติกรรมอื่น ตัวอย่างเช่น PyPy มีการใช้งานการรวบรวมขยะ6แบบ !


24
ในขณะที่ไม่มีการใช้งาน Python อื่น ๆ แต่การพึ่งพารายละเอียดการใช้งานนั้นไม่ได้เป็น Pythonic จริงๆ
Karl Knechtel

มันยังคงใช้งานเฉพาะหรือเป็นมาตรฐานแล้ว? การไม่โทร__exit__()ในกรณีเช่นนี้ดูเหมือนข้อบกพร่องในการออกแบบ
rr-

2
@jgmjgm เป็นเพราะปัญหาทั้งสามประเด็น GC เป็นสิ่งที่คาดเดาไม่ได้try/ finallyเป็นเรื่องตลกและเป็นประโยชน์อย่างมากของตัวจัดการล้างข้อมูลที่withแก้ปัญหาได้ ความแตกต่างระหว่าง "การปิดอย่างชัดเจน" และ "การจัดการกับwith" คือตัวจัดการทางออกเรียกว่าแม้ว่าจะเกิดข้อยกเว้น คุณสามารถใส่close()ในfinallyข้อ แต่ที่ไม่ได้แตกต่างกันมากจากการใช้withแทนเป็น Messier บิต (3 สายพิเศษแทน 1) และเล็ก ๆ น้อย ๆ ยากที่จะได้รับเพียงขวา
เจรจาเดี่ยวสิ้นสุดลง

1
สิ่งที่ฉันไม่เข้าใจนั่นคือสาเหตุที่ 'กับ' จะเชื่อถือได้อีกต่อไปเนื่องจากไม่ชัดเจนเช่นกัน มันเป็นเพราะ spec บอกว่ามันต้องทำแบบนั้นมันจะถูกนำไปใช้อย่างนั้นเสมอ?
jgmjgm

3
@jgmjgm จะเป็นเพราะมีความน่าเชื่อถือมากขึ้นwith foo() as f: [...]เป็นพื้นเดียวกับf = foo(), f.__enter__()[ ... ] และf.__exit__() มีข้อยกเว้นการจัดการเพื่อให้__exit__เรียกว่าเสมอ ดังนั้นไฟล์จะถูกปิดเสมอ
neingeist

104

คุณสามารถใช้pathlib

สำหรับ Python 3.5 และสูงกว่า:

from pathlib import Path
contents = Path(file_path).read_text()

สำหรับ Python เวอร์ชันเก่าให้ใช้pathlib2 :

$ pip install pathlib2

แล้ว:

from pathlib2 import Path
contents = Path(file_path).read_text()

นี่คือการread_text ใช้งานจริง:

def read_text(self, encoding=None, errors=None):
    """
    Open the file in text mode, read it, and close the file.
    """
    with self.open(mode='r', encoding=encoding, errors=errors) as f:
        return f.read()

2

ถ้าคุณต้องอ่านไฟล์ทีละบรรทัดเพื่อทำงานกับแต่ละบรรทัดคุณสามารถใช้

with open('Path/to/file', 'r') as f:
    s = f.readline()
    while s:
        # do whatever you want to
        s = f.readline()

หรือวิธีที่ดีกว่า:

with open('Path/to/file') as f:
    for line in f:
        # do whatever you want to

0

แทนที่จะเรียกเนื้อหาไฟล์เป็นสตริงเดียวมันจะสะดวกในการจัดเก็บเนื้อหาเป็นรายการของทุกบรรทัดที่ไฟล์ประกอบด้วย :

with open('Path/to/file', 'r') as content_file:
    content_list = content_file.read().strip().split("\n")

ตามที่สามารถเห็นได้เราจำเป็นต้องเพิ่มวิธีการต่อข้อมูล.strip().split("\n")เข้ากับคำตอบหลักในชุดข้อความนี้

นี่.strip()เป็นเพียงแค่การลบช่องว่างและตัวอักษรขึ้นบรรทัดใหม่ในตอนจบของสตริงไฟล์ทั้งหมดและ.split("\n")ผลิตรายการที่เกิดขึ้นจริงผ่านทางแยกสตริงไฟล์ทั้งหมดในทุกตัวอักษรขึ้นบรรทัดใหม่ \ n

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

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