Làm cách nào để đọc từng dòng tệp trong Python?


137

Trong thời kỳ tiền sử (Python 1.4), chúng tôi đã làm:

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

Sau Python 2.1, chúng tôi đã làm:

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

trước khi chúng ta có giao thức lặp thuận tiện trong Python 2.3 và có thể làm:

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

Tôi đã thấy một số ví dụ sử dụng dài dòng hơn:

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

Đây có phải là phương pháp ưa thích đi về phía trước?

[sửa] Tôi hiểu rằng câu lệnh with đảm bảo đóng tệp ... nhưng tại sao nó không được bao gồm trong giao thức iterator cho các đối tượng tệp?


4
imho, đề nghị cuối cùng là không dài dòng hơn so với trước đây. Nó chỉ làm được nhiều việc hơn (đảm bảo tệp được đóng khi bạn hoàn thành).
azhrei

1
@azhrei đó là một dòng nữa, vì vậy khách quan thì nó dài dòng hơn.
thebjorn

7
Tôi hiểu những gì bạn đang nói nhưng tôi chỉ nói so sánh táo với táo, gợi ý cuối cùng thứ hai trong bài đăng của bạn cần mã xử lý ngoại lệ cũng như để khớp với tùy chọn cuối cùng. Vì vậy, trong thực tế, nó dài dòng hơn. Tôi đoán nó phụ thuộc vào bối cảnh trong hai lựa chọn cuối cùng là tốt nhất, thực sự.
azhrei

Câu trả lời:


227

Có chính xác một lý do tại sao những điều sau đây được ưa thích:

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

Tất cả chúng ta đều bị làm hỏng bởi sơ đồ đếm tham chiếu tương đối xác định của CPython cho việc thu gom rác. Mặt khác, việc triển khai giả thuyết của Python sẽ không nhất thiết phải đóng tệp "đủ nhanh" mà không có withkhối nếu họ sử dụng một số lược đồ khác để lấy lại bộ nhớ.

Khi triển khai như vậy, bạn có thể gặp lỗi "quá nhiều tệp đang mở" từ HĐH nếu mã của bạn mở tệp nhanh hơn trình thu gom rác gọi bộ hoàn thiện trên các thẻ xử lý tệp mồ côi. Cách giải quyết thông thường là kích hoạt GC ngay lập tức, nhưng đây là một hack khó chịu và nó phải được thực hiện bởi mọi chức năng có thể gặp phải lỗi, bao gồm cả các chức năng trong thư viện. Thật là một cơn ác mộng.

Hoặc bạn chỉ có thể sử dụng withkhối.

Câu hỏi thưởng

(Ngừng đọc ngay bây giờ nếu chỉ quan tâm đến các khía cạnh khách quan của câu hỏi.)

Tại sao không có trong giao thức iterator cho các đối tượng tệp?

Đây là một câu hỏi chủ quan về thiết kế API, vì vậy tôi có một câu trả lời chủ quan trong hai phần.

Ở mức độ ruột, điều này cảm thấy sai, bởi vì nó làm cho giao thức iterator thực hiện hai việc riêng biệt, lặp đi lặp lại trên các dòng đóng tệp xử lý, và thường thì một chức năng trông đơn giản thực hiện hai hành động. Trong trường hợp này, nó cảm thấy đặc biệt tệ bởi vì các trình vòng lặp liên quan theo cách gần như chức năng, dựa trên giá trị với nội dung của tệp, nhưng quản lý xử lý tệp là một nhiệm vụ hoàn toàn riêng biệt. Việc nghiền nát cả hai, vô hình chung, thành một hành động, gây ngạc nhiên cho những người đọc mã và gây khó khăn hơn cho lý do về hành vi của chương trình.

Các ngôn ngữ khác về cơ bản đã đi đến kết luận tương tự. Haskell đã nhanh chóng tán tỉnh cái gọi là "IO lười biếng", cho phép bạn lặp lại một tập tin và nó tự động đóng lại khi bạn đến cuối luồng, nhưng gần như không khuyến khích sử dụng IO lười biếng trong Haskell những ngày này và Haskell Người dùng chủ yếu đã chuyển sang quản lý tài nguyên rõ ràng hơn như Conduit hoạt động giống như withkhối trong Python.

Ở cấp độ kỹ thuật, có một số điều bạn có thể muốn thực hiện với trình xử lý tệp trong Python sẽ không hoạt động tốt nếu lặp lại đóng xử lý tệp. Ví dụ: giả sử tôi cần lặp lại tập tin hai lần:

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

Mặc dù đây là trường hợp sử dụng ít phổ biến hơn, hãy xem xét thực tế rằng tôi có thể vừa thêm ba dòng mã ở dưới cùng vào cơ sở mã hiện có ban đầu có ba dòng trên cùng. Nếu lặp lại đóng tập tin, tôi sẽ không thể làm điều đó. Vì vậy, việc giữ việc lặp lại và quản lý tài nguyên riêng biệt giúp việc soạn các đoạn mã thành một chương trình Python lớn hơn, hoạt động dễ dàng hơn.

Khả năng tương thích là một trong những tính năng sử dụng quan trọng nhất của ngôn ngữ hoặc API.


1
+1 vì nó giải thích "khi nào" trong nhận xét của tôi về op ;-)
azhrei

ngay cả với việc triển khai thay thế, trình xử lý với sẽ chỉ gây ra sự cố cho các chương trình mở hàng trăm tệp trong một chuỗi rất nhanh. Hầu hết các chương trình có thể nhận được bằng cách tham chiếu tệp lơ lửng mà không có vấn đề. Trừ khi bạn vô hiệu hóa nó, cuối cùng, GC sẽ khởi động và xóa phần xử lý tệp. withmặc dù mang lại cho bạn sự an tâm, vì vậy đây vẫn là một cách tốt nhất.
Lie Ryan

1
@DietrichEpp: có lẽ "tham chiếu tệp lơ lửng" không phải là từ đúng, tôi thực sự có nghĩa là các tệp xử lý không còn truy cập được nhưng chưa được đóng. Trong mọi trường hợp, GC sẽ đóng xử lý tệp khi nó thu thập đối tượng tệp, do đó, miễn là bạn không có thêm các tham chiếu đến đối tượng tệp và bạn không tắt GC và bạn không mở nhanh nhiều tệp kế tiếp, bạn không thể nhận được "quá nhiều tệp đang mở" do không đóng tệp.
Lie Ryan

1
Vâng, đó chính xác là những gì tôi muốn nói bởi "nếu mã của bạn mở tệp nhanh hơn trình thu gom rác gọi bộ hoàn thiện trên tay cầm tệp mồ côi".
Dietrich Epp

1
Lý do lớn hơn để sử dụng là nếu bạn không đóng tệp, nó sẽ không nhất thiết phải được viết ngay lập tức.
Antimon

20

Đúng,

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

là con đường để đi

Nó không dài dòng hơn. Nó an toàn hơn.


5

nếu bạn bị tắt bởi dòng phụ, bạn có thể sử dụng chức năng bao bọc như vậy:

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

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

trong Python 3.3, yield fromcâu lệnh sẽ làm cho điều này thậm chí còn ngắn hơn:

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

2
gọi hàm xreadlines .. và đặt nó vào một tệp có tên xreadlines.py và chúng tôi quay lại cú pháp Python 2.1 :-)
thebjorn

@thebjorn: có lẽ, nhưng ví dụ Python 2.1 mà bạn đã trích dẫn không an toàn trước trình xử lý tệp không được tiết lộ trong các triển khai thay thế. Việc đọc tệp Python 2.1 an toàn từ trình xử lý tệp không được tiết lộ sẽ mất ít nhất 5 dòng.
Lie Ryan

-2
f = open('test.txt','r')
for line in f.xreadlines():
    print line
f.close()

5
Điều này không thực sự trả lời câu hỏi
Thayne
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.