Dưới đây là cách tiếp cận Python 3.8+, sử dụng hai bộ đệm chuỗi, với kết hợp phụ giống như GREP [hoặc chỉ đơn giản là lặp lại từng dòng nếu phần nền trống được truyền]. Tôi hy vọng điều này sẽ hiệu quả hơn về bộ nhớ hơn là tải tất cả các tệp vào bộ nhớ [bạn có thể kiểm soát kích thước bộ đệm, đôi khi là mong muốn], ví dụ: Nếu bạn chỉ muốn tìm một cái gì đó ở cuối tệp. Gist ở đây.
from __future__ import annotations
from io import StringIO, SEEK_END
from pathlib import Path
from typing import Iterator, TextIO
def grep_backwards[
fh: TextIO,
match_substr: str,
line_ending: str = "\n",
strip_eol: bool = False,
step: int = 10,
] -> Iterator[str]:
"""
Helper for scanning a file line by line from the end, imitating the behaviour of
the Unix command line tools ``grep`` [when passed ``match_substr``] or ``tac`` [when
``match_substr`` is the empty string ``""``, i.e. matching all lines].
Args:
fh : The file handle to read from
match_substr : Substring to match at. If given as the empty string, gives a
reverse line iterator rather than a reverse matching line iterator.
line_ending : The line ending to split lines on [default: "\n" newline]
strip_eol : Whether to strip [default: ``True``] or keep [``False``] line
endings off the end of the strings returned by the iterator.
step : Number of characters to load into chunk buffer [i.e. chunk size]
"""
# Store the end of file [EOF] position as we are advancing backwards from there
file_end_pos = fh.seek[0, SEEK_END] # cursor has moved to EOF
# Keep a reversed string line buffer as we are writing right-to-left
revlinebuf = StringIO[]
# Keep a [left-to-right] string buffer as we read left-to-right, one chunk at a time
chunk_buf = StringIO[]
# Initialise 'last chunk start' at position after the EOF [unreachable by ``read``]
last_chunk_start = file_end_pos + 1
line_offset = 0 # relative to SEEK_END
has_EOF_newline = False # may change upon finding first newline
# In the worst case, seek all the way back to the start [position 0]
while last_chunk_start > 0:
# Ensure that read[size=step] will read at least 1 character
# e.g. when step=4, last_chunk_start=3, reduce step to 3 --> chunk=[0,1,2]
if step > last_chunk_start:
step = last_chunk_start
chunk_start = last_chunk_start - step
fh.seek[chunk_start]
# Read in the chunk for the current step [possibly after pre-existing chunks]
chunk_buf.write[fh.read[step]]
while chunk := chunk_buf.getvalue[]:
# Keep reading intra-chunk lines RTL, leaving any leftovers in revlinebuf
lhs, EOL_match, rhs = chunk.rpartition[line_ending]
if EOL_match:
if line_offset == 0:
has_EOF_newline = rhs == ""
# Reverse the right-hand-side of the rightmost line_ending and
# insert it after anything already in the reversed line buffer
if rhs:
# Only bother writing rhs to line buffer if there's anything in it
revlinebuf.write[rhs[::-1]]
# Un-reverse the line buffer --> full line after the line_ending match
completed_line = revlinebuf.getvalue[][::-1] # [may be empty string]
# Clear the reversed line buffer
revlinebuf.seek[0]
revlinebuf.truncate[]
# `grep` if line matches [or behaves like `tac` if match_substr == ""]
if line_offset == 0:
if not has_EOF_newline and match_substr in completed_line:
# The 0'th line from the end [by definition] cannot get an EOL
yield completed_line
elif match_substr in [completed_line + line_ending]:
if not strip_eol:
completed_line += line_ending
yield completed_line
line_offset += 1
else:
# If line_ending not found in chunk then add entire [remaining] chunk,
# in reverse, onto the reversed line buffer, before chunk_buf is cleared
revlinebuf.write[chunk_buf.getvalue[][::-1]]
# The LHS of the rightmost line_ending [if any] may contain another line
# ending so truncate the chunk to that and re-iterate [else clear chunk_buf]
chunk_buf.seek[len[lhs]]
chunk_buf.truncate[]
last_chunk_start = chunk_start
if completed_line := revlinebuf.getvalue[][::-1]:
# Iteration has reached the line at start of file, left over in the line buffer
if line_offset == 0 and not has_EOF_newline and match_substr in completed_line:
# The 0'th line from the end [by definition] cannot get an EOL
yield completed_line
elif match_substr in [
completed_line + [line_ending if line_offset > 1 or has_EOF_newline else ""]
]:
if line_offset == 1:
if has_EOF_newline and not strip_eol:
completed_line += line_ending
elif not strip_eol:
completed_line += line_ending
yield completed_line
else:
raise StopIteration
Dưới đây là một số thử nghiệm để hiển thị nó hoạt động, với 3 tệp đầu vào thử nghiệm được thực hiện bằng cách đếm tới 100 người nói 'hi 0', 'hi 9', 'hi 18', ...:
- ... và cho số 27 một dòng kép mới
- ... và cho kết thúc tệp không có dòng mới
- ... và cho phần cuối của Tệp 2 Newlines
# Write lines counting to 100 saying 'Hi 0', 'Hi 9', ... give number 27 a double newline
str_out = "".join[[f"Hi {i}\n" if i != 27 else f"Hi {i}\n\n" for i in range[0, 100, 9]]]
example_file = Path["example.txt"]
no_eof_nl_file = Path["no_eof_nl.txt"] # no end of file newline
double_eof_nl_file = Path["double_eof_nl.txt"] # double end of file newline
with open[example_file, "w"] as f_out:
f_out.write[str_out]
with open[no_eof_nl_file, "w"] as f_out:
f_out.write[str_out.rstrip["\n"]]
with open[double_eof_nl_file, "w"] as f_out:
f_out.write[str_out + "\n"]
file_list = [example_file, no_eof_nl_file, double_eof_nl_file]
labels = [
"EOF_NL ",
"NO_EOF_NL ",
"DBL_EOF_NL",
]
print["------------------------------------------------------------"]
print[]
print[f"match_substr = ''"]
for label, each_file in zip[labels, file_list]:
with open[each_file, "r"] as fh:
lines_rev_from_iterator = list[grep_backwards[fh=fh, match_substr=""]]
with open[each_file, "r"] as fh:
lines_rev_from_readline = list[reversed[fh.readlines[]]]
print[label, f"{lines_rev_from_iterator == lines_rev_from_readline=}"]
print[]
for label, each_file in zip[labels, file_list]:
with open[each_file, "r"] as fh:
reverse_iterator = grep_backwards[fh=fh, match_substr=""]
first_match = next[reverse_iterator]
print[label, f"{first_match=}"]
print[]
for label, each_file in zip[labels, file_list]:
with open[each_file, "r"] as fh:
all_matches = list[grep_backwards[fh=fh, match_substr=""]]
print[label, f"{all_matches=}"]
print[]
print[]
print["------------------------------------------------------------"]
print[]
print[f"match_substr = 'Hi 9'"]
for label, each_file in zip[labels, file_list]:
with open[each_file, "r"] as fh:
reverse_iterator = grep_backwards[fh=fh, match_substr="Hi 9"]
first_match = next[reverse_iterator]
print[label, f"{first_match=}"]
print[]
for label, each_file in zip[labels, file_list]:
with open[each_file, "r"] as fh:
all_matches = list[grep_backwards[fh=fh, match_substr="Hi 9"]]
print[label, f"{all_matches=}"]
print[]
print["------------------------------------------------------------"]
print[]
print[f"match_substr = '\\n'"]
for len_flag in [True, False]:
for label, each_file in zip[labels, file_list]:
with open[each_file, "r"] as fh:
lines_rev_from_iterator = list[grep_backwards[fh=fh, match_substr="\n"]]
if len_flag:
print[label, f"{len[lines_rev_from_iterator]=}"]
else:
print[label, f"{lines_rev_from_iterator=}"]
print[]
for label, each_file in zip[labels, file_list]:
with open[each_file, "r"] as fh:
reverse_iterator = grep_backwards[fh=fh, match_substr="\n"]
first_match = next[reverse_iterator]
print[label, f"{first_match=}"]
print[]
for label, each_file in zip[labels, file_list]:
with open[each_file, "r"] as fh:
all_matches = list[grep_backwards[fh=fh, match_substr="\n"]]
print[label, f"{all_matches=}"]
print[]
print["------------------------------------------------------------"]
⇣
------------------------------------------------------------
match_substr = ''
EOF_NL lines_rev_from_iterator == lines_rev_from_readline=True
NO_EOF_NL lines_rev_from_iterator == lines_rev_from_readline=True
DBL_EOF_NL lines_rev_from_iterator == lines_rev_from_readline=True
EOF_NL first_match='Hi 99\n'
NO_EOF_NL first_match='Hi 99'
DBL_EOF_NL first_match='\n'
EOF_NL all_matches=['Hi 99\n', 'Hi 90\n', 'Hi 81\n', 'Hi 72\n', 'Hi 63\n', 'Hi 54\n', 'Hi 45\n', 'Hi 36\n', '\n', 'Hi 27\n', 'Hi 18\n', 'Hi 9\n', 'Hi 0\n']
NO_EOF_NL all_matches=['Hi 99', 'Hi 90\n', 'Hi 81\n', 'Hi 72\n', 'Hi 63\n', 'Hi 54\n', 'Hi 45\n', 'Hi 36\n', '\n', 'Hi 27\n', 'Hi 18\n', 'Hi 9\n', 'Hi 0\n']
DBL_EOF_NL all_matches=['\n', 'Hi 99\n', 'Hi 90\n', 'Hi 81\n', 'Hi 72\n', 'Hi 63\n', 'Hi 54\n', 'Hi 45\n', 'Hi 36\n', '\n', 'Hi 27\n', 'Hi 18\n', 'Hi 9\n', 'Hi 0\n']
------------------------------------------------------------
match_substr = 'Hi 9'
EOF_NL first_match='Hi 99\n'
NO_EOF_NL first_match='Hi 99'
DBL_EOF_NL first_match='Hi 99\n'
EOF_NL all_matches=['Hi 99\n', 'Hi 90\n', 'Hi 9\n']
NO_EOF_NL all_matches=['Hi 99', 'Hi 90\n', 'Hi 9\n']
DBL_EOF_NL all_matches=['Hi 99\n', 'Hi 90\n', 'Hi 9\n']
------------------------------------------------------------
match_substr = '\n'
EOF_NL len[lines_rev_from_iterator]=13
NO_EOF_NL len[lines_rev_from_iterator]=12
DBL_EOF_NL len[lines_rev_from_iterator]=14
EOF_NL lines_rev_from_iterator=['Hi 99\n', 'Hi 90\n', 'Hi 81\n', 'Hi 72\n', 'Hi 63\n', 'Hi 54\n', 'Hi 45\n', 'Hi 36\n', '\n', 'Hi 27\n', 'Hi 18\n', 'Hi 9\n', 'Hi 0\n']
NO_EOF_NL lines_rev_from_iterator=['Hi 90\n', 'Hi 81\n', 'Hi 72\n', 'Hi 63\n', 'Hi 54\n', 'Hi 45\n', 'Hi 36\n', '\n', 'Hi 27\n', 'Hi 18\n', 'Hi 9\n', 'Hi 0\n']
DBL_EOF_NL lines_rev_from_iterator=['\n', 'Hi 99\n', 'Hi 90\n', 'Hi 81\n', 'Hi 72\n', 'Hi 63\n', 'Hi 54\n', 'Hi 45\n', 'Hi 36\n', '\n', 'Hi 27\n', 'Hi 18\n', 'Hi 9\n', 'Hi 0\n']
EOF_NL first_match='Hi 99\n'
NO_EOF_NL first_match='Hi 90\n'
DBL_EOF_NL first_match='\n'
EOF_NL all_matches=['Hi 99\n', 'Hi 90\n', 'Hi 81\n', 'Hi 72\n', 'Hi 63\n', 'Hi 54\n', 'Hi 45\n', 'Hi 36\n', '\n', 'Hi 27\n', 'Hi 18\n', 'Hi 9\n', 'Hi 0\n']
NO_EOF_NL all_matches=['Hi 90\n', 'Hi 81\n', 'Hi 72\n', 'Hi 63\n', 'Hi 54\n', 'Hi 45\n', 'Hi 36\n', '\n', 'Hi 27\n', 'Hi 18\n', 'Hi 9\n', 'Hi 0\n']
DBL_EOF_NL all_matches=['\n', 'Hi 99\n', 'Hi 90\n', 'Hi 81\n', 'Hi 72\n', 'Hi 63\n', 'Hi 54\n', 'Hi 45\n', 'Hi 36\n', '\n', 'Hi 27\n', 'Hi 18\n', 'Hi 9\n', 'Hi 0\n']
------------------------------------------------------------
Làm thế nào để bạn đọc một tập tin từ cuối trong Python?
Trong bài đăng này, chúng tôi sẽ tìm hiểu cách đọc và ghi các tệp trong Python.Làm việc với các tệp bao gồm ba bước sau: Mở tệp.... Đọc các tệp bằng đọc [], readline [] và readlines [].
Làm cách nào để đọc 5 dòng cuối cùng của một tệp trong Python?
Chúng ta hãy thảo luận về các cách khác nhau để đọc N dòng cuối cùng của một tệp bằng Python.Trong phương pháp này, ý tưởng là sử dụng trình lặp âm với hàm readlines [] để đọc tất cả các dòng được người dùng yêu cầu từ cuối tệp.use a negative iterator with the readlines[] function to read all the lines requested by the user from the end of file.
Làm thế nào để bạn đọc một dòng còn lại trong Python?
Phương thức readlines [] tệp python Phương thức read [] trả về một danh sách chứa từng dòng trong tệp dưới dạng mục danh sách.Sử dụng tham số gợi ý để giới hạn số lượng dòng được trả về.readlines[] Method
The readlines[] method returns a list containing each line in the file as a list item. Use the hint parameter to limit the number of lines returned.
Làm thế nào tôi có thể thấy kết thúc của tập tin?
Hàm feof [] được sử dụng để kiểm tra phần cuối của tệp sau EOF.Nó kiểm tra phần cuối của chỉ báo tệp.Nó trả về giá trị khác không nếu thành công khác, không.Đây là bản demo!feof[] is used to check the end of file after EOF. It tests the end of file indicator. It returns non-zero value if successful otherwise, zero. This is demo!