Làm thế nào để đọc một tập tin theo thứ tự ngược bằng python? Tôi muốn đọc một tập tin từ dòng cuối cùng đến dòng đầu tiên.
Làm thế nào để đọc một tập tin theo thứ tự ngược bằng python? Tôi muốn đọc một tập tin từ dòng cuối cùng đến dòng đầu tiên.
Câu trả lời:
for line in reversed(open("filename").readlines()):
print line.rstrip()
Và trong Python 3:
for line in reversed(list(open("filename"))):
print(line.rstrip())
with
tuyên bố thường khá đau đớn.
Một câu trả lời đúng, hiệu quả được viết như một máy phát điện.
import os
def reverse_readline(filename, buf_size=8192):
"""A generator that returns the lines of a file in reverse order"""
with open(filename) as fh:
segment = None
offset = 0
fh.seek(0, os.SEEK_END)
file_size = remaining_size = fh.tell()
while remaining_size > 0:
offset = min(file_size, offset + buf_size)
fh.seek(file_size - offset)
buffer = fh.read(min(remaining_size, buf_size))
remaining_size -= buf_size
lines = buffer.split('\n')
# The first line of the buffer is probably not a complete line so
# we'll save it and append it to the last line of the next buffer
# we read
if segment is not None:
# If the previous chunk starts right from the beginning of line
# do not concat the segment to the last line of new chunk.
# Instead, yield the segment first
if buffer[-1] != '\n':
lines[-1] += segment
else:
yield segment
segment = lines[0]
for index in range(len(lines) - 1, 0, -1):
if lines[index]:
yield lines[index]
# Don't yield None if the file was empty
if segment is not None:
yield segment
fh.seek(0, os.SEEK_END)
và thay đổi fh.seek(-offset, os.SEEK_END)
quá fh.seek(file_size - offset)
.
utf8
) seek()
và read()
tham khảo các kích thước khác nhau. Đó có lẽ cũng là lý do tại sao đối số đầu tiên khác không seek()
liên quan đến os.SEEK_END
không được hỗ trợ.
'aöaö'.encode()
là b'a\xc3\xb6a\xc3\xb6'
. Nếu bạn lưu nó vào đĩa và sau đó đọc ở chế độ văn bản, khi bạn thực hiện seek(2)
nó sẽ di chuyển theo hai byte, do đó seek(2); read(1)
sẽ gây ra lỗi UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb6 in position 0: invalid start byte
, nhưng nếu bạn làm như vậy seek(0); read(2); read(1)
, bạn sẽ nhận được điều 'a'
bạn mong đợi, đó là: seek()
không bao giờ được mã hóa -biết, read()
là nếu bạn mở tệp ở chế độ văn bản. Bây giờ nếu có 'aöaö' * 1000000
, các khối của bạn sẽ không được căn chỉnh chính xác.
Còn những thứ như thế này thì sao:
import os
def readlines_reverse(filename):
with open(filename) as qfile:
qfile.seek(0, os.SEEK_END)
position = qfile.tell()
line = ''
while position >= 0:
qfile.seek(position)
next_char = qfile.read(1)
if next_char == "\n":
yield line[::-1]
line = ''
else:
line += next_char
position -= 1
yield line[::-1]
if __name__ == '__main__':
for qline in readlines_reverse(raw_input()):
print qline
Vì tệp được đọc theo từng ký tự theo thứ tự ngược lại, nên nó sẽ hoạt động ngay cả trên các tệp rất lớn, miễn là các dòng riêng lẻ phù hợp với bộ nhớ.
Bạn cũng có thể sử dụng mô-đun python file_read_backwards
.
Sau khi cài đặt nó, thông qua pip install file_read_backwards
(v1.2.1), bạn có thể đọc toàn bộ tệp ngược (dòng thông minh) theo cách hiệu quả bộ nhớ thông qua:
#!/usr/bin/env python2.7
from file_read_backwards import FileReadBackwards
with FileReadBackwards("/path/to/file", encoding="utf-8") as frb:
for l in frb:
print l
Nó hỗ trợ mã hóa "utf-8", "latin-1" và "ascii".
Hỗ trợ cũng có sẵn cho python3. Tài liệu khác có thể được tìm thấy tại http://file-read-backwards.readthedocs.io/en/latest/readme.html
for line in reversed(open("file").readlines()):
print line.rstrip()
Nếu bạn đang dùng linux, bạn có thể sử dụng tac
lệnh.
$ tac file
2 công thức bạn có thể tìm thấy trong ActiveState tại đây và đây
__reversed__()
phương thức là cần thiết, nhưng python2.5 không phàn nàn về một lớp tùy chỉnh mà không có nó.
__reversed__
phương pháp cũng không cần thiết, và đã không sử dụng để trở thành một thứ như vậy. Nếu một đối tượng cung cấp __len__
và __getitem__
nó sẽ hoạt động tốt (trừ một số trường hợp ngoại lệ, chẳng hạn như dict).
__reversed__
?
import re
def filerev(somefile, buffer=0x20000):
somefile.seek(0, os.SEEK_END)
size = somefile.tell()
lines = ['']
rem = size % buffer
pos = max(0, (size // buffer - 1) * buffer)
while pos >= 0:
somefile.seek(pos, os.SEEK_SET)
data = somefile.read(rem + buffer) + lines[0]
rem = 0
lines = re.findall('[^\n]*\n?', data)
ix = len(lines) - 2
while ix > 0:
yield lines[ix]
ix -= 1
pos -= buffer
else:
yield lines[0]
with open(sys.argv[1], 'r') as f:
for line in filerev(f):
sys.stdout.write(line)
Câu trả lời được chấp nhận sẽ không hoạt động đối với các trường hợp có tệp lớn không phù hợp với bộ nhớ (đây không phải là trường hợp hiếm).
Như đã được người khác lưu ý, câu trả lời @srohde có vẻ tốt, nhưng nó có vấn đề tiếp theo:
ngay cả khi chúng tôi tái cấu trúc để chấp nhận đối tượng tệp, nó sẽ không hoạt động cho tất cả các mã hóa: chúng tôi có thể chọn tệp có utf-8
nội dung mã hóa và không phải mã ascii như
й
vượt qua buf_size
bằng 1
và sẽ có
UnicodeDecodeError: 'utf8' codec can't decode byte 0xb9 in position 0: invalid start byte
tất nhiên văn bản có thể lớn hơn nhưng buf_size
có thể được chọn để dẫn đến lỗi bị che khuất như trên,
Vì vậy, xem xét tất cả những mối quan tâm này, tôi đã viết các chức năng riêng biệt:
Trước hết hãy xác định các chức năng tiện ích tiếp theo:
ceil_division
để thực hiện phân chia với trần nhà (ngược lại với //
phân chia tiêu chuẩn với sàn, có thể tìm thấy thêm thông tin trong chủ đề này )
def ceil_division(left_number, right_number):
"""
Divides given numbers with ceiling.
"""
return -(-left_number // right_number)
split
để tách chuỗi bằng dấu phân cách đã cho từ đầu bên phải với khả năng giữ chuỗi:
def split(string, separator, keep_separator):
"""
Splits given string by given separator.
"""
parts = string.split(separator)
if keep_separator:
*parts, last_part = parts
parts = [part + separator for part in parts]
if last_part:
return parts + [last_part]
return parts
read_batch_from_end
để đọc lô từ cuối bên phải của luồng nhị phân
def read_batch_from_end(byte_stream, size, end_position):
"""
Reads batch from the end of given byte stream.
"""
if end_position > size:
offset = end_position - size
else:
offset = 0
size = end_position
byte_stream.seek(offset)
return byte_stream.read(size)
Sau đó, chúng ta có thể định nghĩa hàm để đọc luồng byte theo thứ tự ngược lại như
import functools
import itertools
import os
from operator import methodcaller, sub
def reverse_binary_stream(byte_stream, batch_size=None,
lines_separator=None,
keep_lines_separator=True):
if lines_separator is None:
lines_separator = (b'\r', b'\n', b'\r\n')
lines_splitter = methodcaller(str.splitlines.__name__,
keep_lines_separator)
else:
lines_splitter = functools.partial(split,
separator=lines_separator,
keep_separator=keep_lines_separator)
stream_size = byte_stream.seek(0, os.SEEK_END)
if batch_size is None:
batch_size = stream_size or 1
batches_count = ceil_division(stream_size, batch_size)
remaining_bytes_indicator = itertools.islice(
itertools.accumulate(itertools.chain([stream_size],
itertools.repeat(batch_size)),
sub),
batches_count)
try:
remaining_bytes_count = next(remaining_bytes_indicator)
except StopIteration:
return
def read_batch(position):
result = read_batch_from_end(byte_stream,
size=batch_size,
end_position=position)
while result.startswith(lines_separator):
try:
position = next(remaining_bytes_indicator)
except StopIteration:
break
result = (read_batch_from_end(byte_stream,
size=batch_size,
end_position=position)
+ result)
return result
batch = read_batch(remaining_bytes_count)
segment, *lines = lines_splitter(batch)
yield from reverse(lines)
for remaining_bytes_count in remaining_bytes_indicator:
batch = read_batch(remaining_bytes_count)
lines = lines_splitter(batch)
if batch.endswith(lines_separator):
yield segment
else:
lines[-1] += segment
segment, *lines = lines
yield from reverse(lines)
yield segment
và cuối cùng, một chức năng đảo ngược tệp văn bản có thể được định nghĩa như:
import codecs
def reverse_file(file, batch_size=None,
lines_separator=None,
keep_lines_separator=True):
encoding = file.encoding
if lines_separator is not None:
lines_separator = lines_separator.encode(encoding)
yield from map(functools.partial(codecs.decode,
encoding=encoding),
reverse_binary_stream(
file.buffer,
batch_size=batch_size,
lines_separator=lines_separator,
keep_lines_separator=keep_lines_separator))
Tôi đã tạo 4 tệp bằng fsutil
lệnh :
Ngoài ra, tôi đã tái cấu trúc giải pháp @srohde để làm việc với đối tượng tệp thay vì đường dẫn tệp.
from timeit import Timer
repeats_count = 7
number = 1
create_setup = ('from collections import deque\n'
'from __main__ import reverse_file, reverse_readline\n'
'file = open("{}")').format
srohde_solution = ('with file:\n'
' deque(reverse_readline(file,\n'
' buf_size=8192),'
' maxlen=0)')
azat_ibrakov_solution = ('with file:\n'
' deque(reverse_file(file,\n'
' lines_separator="\\n",\n'
' keep_lines_separator=False,\n'
' batch_size=8192), maxlen=0)')
print('reversing empty file by "srohde"',
min(Timer(srohde_solution,
create_setup('empty.txt')).repeat(repeats_count, number)))
print('reversing empty file by "Azat Ibrakov"',
min(Timer(azat_ibrakov_solution,
create_setup('empty.txt')).repeat(repeats_count, number)))
print('reversing tiny file (1MB) by "srohde"',
min(Timer(srohde_solution,
create_setup('tiny.txt')).repeat(repeats_count, number)))
print('reversing tiny file (1MB) by "Azat Ibrakov"',
min(Timer(azat_ibrakov_solution,
create_setup('tiny.txt')).repeat(repeats_count, number)))
print('reversing small file (10MB) by "srohde"',
min(Timer(srohde_solution,
create_setup('small.txt')).repeat(repeats_count, number)))
print('reversing small file (10MB) by "Azat Ibrakov"',
min(Timer(azat_ibrakov_solution,
create_setup('small.txt')).repeat(repeats_count, number)))
print('reversing large file (50MB) by "srohde"',
min(Timer(srohde_solution,
create_setup('large.txt')).repeat(repeats_count, number)))
print('reversing large file (50MB) by "Azat Ibrakov"',
min(Timer(azat_ibrakov_solution,
create_setup('large.txt')).repeat(repeats_count, number)))
Lưu ý : Tôi đã sử dụng collections.deque
lớp để xả khí.
Đối với PyPy 3.5 trên Windows 10:
reversing empty file by "srohde" 8.31e-05
reversing empty file by "Azat Ibrakov" 0.00016090000000000028
reversing tiny file (1MB) by "srohde" 0.160081
reversing tiny file (1MB) by "Azat Ibrakov" 0.09594989999999998
reversing small file (10MB) by "srohde" 8.8891863
reversing small file (10MB) by "Azat Ibrakov" 5.323388100000001
reversing large file (50MB) by "srohde" 186.5338368
reversing large file (50MB) by "Azat Ibrakov" 99.07450229999998
Đối với CPython 3.5 trên Windows 10:
reversing empty file by "srohde" 3.600000000000001e-05
reversing empty file by "Azat Ibrakov" 4.519999999999958e-05
reversing tiny file (1MB) by "srohde" 0.01965560000000001
reversing tiny file (1MB) by "Azat Ibrakov" 0.019207699999999994
reversing small file (10MB) by "srohde" 3.1341862999999996
reversing small file (10MB) by "Azat Ibrakov" 3.0872588000000007
reversing large file (50MB) by "srohde" 82.01206720000002
reversing large file (50MB) by "Azat Ibrakov" 82.16775059999998
Vì vậy, như chúng ta có thể thấy nó thực hiện như giải pháp ban đầu, nhưng tổng quát hơn và không có nhược điểm được liệt kê ở trên.
Tôi đã thêm 0.3.0
phiên bản này vào phiên bản của lz
gói (yêu cầu Python 3.5 +) có nhiều tiện ích chức năng / lặp được kiểm tra tốt.
Có thể được sử dụng như
import io
from lz.iterating import reverse
...
with open('path/to/file') as file:
for line in reverse(file, batch_size=io.DEFAULT_BUFFER_SIZE):
print(line)
Nó hỗ trợ tất cả các mã hóa tiêu chuẩn (có thể ngoại trừ utf-7
vì tôi khó xác định chiến lược tạo chuỗi có thể mã hóa với nó).
Ở đây bạn có thể tìm thấy triển khai của tôi, bạn có thể giới hạn việc sử dụng ram bằng cách thay đổi biến "bộ đệm", có một lỗi là chương trình in một dòng trống ngay từ đầu.
Và việc sử dụng ram cũng có thể tăng nếu không có dòng mới nào nhiều hơn byte bộ đệm, biến "rò rỉ" sẽ tăng cho đến khi nhìn thấy một dòng mới ("\ n").
Điều này cũng hoạt động đối với các tệp 16 GB lớn hơn tổng bộ nhớ của tôi.
import os,sys
buffer = 1024*1024 # 1MB
f = open(sys.argv[1])
f.seek(0, os.SEEK_END)
filesize = f.tell()
division, remainder = divmod(filesize, buffer)
line_leak=''
for chunk_counter in range(1,division + 2):
if division - chunk_counter < 0:
f.seek(0, os.SEEK_SET)
chunk = f.read(remainder)
elif division - chunk_counter >= 0:
f.seek(-(buffer*chunk_counter), os.SEEK_END)
chunk = f.read(buffer)
chunk_lines_reversed = list(reversed(chunk.split('\n')))
if line_leak: # add line_leak from previous chunk to beginning
chunk_lines_reversed[0] += line_leak
# after reversed, save the leakedline for next chunk iteration
line_leak = chunk_lines_reversed.pop()
if chunk_lines_reversed:
print "\n".join(chunk_lines_reversed)
# print the last leaked line
if division - chunk_counter < 0:
print line_leak
Cảm ơn câu trả lời @srohde. Nó có một lỗi nhỏ kiểm tra ký tự dòng mới với toán tử 'là' và tôi không thể nhận xét về câu trả lời với 1 danh tiếng. Ngoài ra, tôi muốn quản lý tệp mở bên ngoài vì điều đó cho phép tôi nhúng các đoạn lan man của mình cho các tác vụ luigi.
Những gì tôi cần thay đổi có dạng:
with open(filename) as fp:
for line in fp:
#print line, # contains new line
print '>{}<'.format(line)
Tôi muốn đổi thành:
with open(filename) as fp:
for line in reversed_fp_iter(fp, 4):
#print line, # contains new line
print '>{}<'.format(line)
Đây là một câu trả lời sửa đổi muốn xử lý tệp và giữ dòng mới:
def reversed_fp_iter(fp, buf_size=8192):
"""a generator that returns the lines of a file in reverse order
ref: https://stackoverflow.com/a/23646049/8776239
"""
segment = None # holds possible incomplete segment at the beginning of the buffer
offset = 0
fp.seek(0, os.SEEK_END)
file_size = remaining_size = fp.tell()
while remaining_size > 0:
offset = min(file_size, offset + buf_size)
fp.seek(file_size - offset)
buffer = fp.read(min(remaining_size, buf_size))
remaining_size -= buf_size
lines = buffer.splitlines(True)
# the first line of the buffer is probably not a complete line so
# we'll save it and append it to the last line of the next buffer
# we read
if segment is not None:
# if the previous chunk starts right from the beginning of line
# do not concat the segment to the last line of new chunk
# instead, yield the segment first
if buffer[-1] == '\n':
#print 'buffer ends with newline'
yield segment
else:
lines[-1] += segment
#print 'enlarged last line to >{}<, len {}'.format(lines[-1], len(lines))
segment = lines[0]
for index in range(len(lines) - 1, 0, -1):
if len(lines[index]):
yield lines[index]
# Don't yield None if the file was empty
if segment is not None:
yield segment
một chức năng đơn giản để tạo một tập tin thứ hai đảo ngược (chỉ linux):
import os
def tac(file1, file2):
print(os.system('tac %s > %s' % (file1,file2)))
cách sử dụng
tac('ordered.csv', 'reversed.csv')
f = open('reversed.csv')
mv mycontent.txt $'hello $(rm -rf $HOME) world.txt'
hoặc tương tự bằng cách sử dụng tên tệp đầu ra do người dùng không tin cậy cung cấp? Nếu bạn muốn xử lý tên tập tin tùy ý một cách an toàn, cần thận trọng hơn. subprocess.Popen(['tac', file1], stdout=open(file2, 'w'))
sẽ được an toàn, ví dụ.
Nếu bạn lo ngại về kích thước tập tin / mức sử dụng bộ nhớ, ánh xạ bộ nhớ tệp và quét ngược để tìm dòng mới là một giải pháp:
với mở ("tên tệp") là f:
print(f.read()[::-1])
list(reversed(f.read()))
.
def reverse_lines(filename):
y=open(filename).readlines()
return y[::-1]
Luôn sử dụng with
khi làm việc với các tệp vì nó xử lý mọi thứ cho bạn:
with open('filename', 'r') as f:
for line in reversed(f.readlines()):
print line
Hoặc trong Python 3:
with open('filename', 'r') as f:
for line in reversed(list(f.readlines())):
print(line)
trước tiên bạn cần mở tệp của mình ở định dạng đọc, lưu nó vào một biến, sau đó mở tệp thứ hai ở định dạng ghi nơi bạn sẽ viết hoặc nối thêm biến bằng cách sử dụng lát [:: - 1], đảo ngược hoàn toàn tệp. Bạn cũng có thể sử dụng readlines () để biến nó thành một danh sách các dòng mà bạn có thể thao tác
def copy_and_reverse(filename, newfile):
with open(filename) as file:
text = file.read()
with open(newfile, "w") as file2:
file2.write(text[::-1])
Hầu hết các câu trả lời cần phải đọc toàn bộ tập tin trước khi làm bất cứ điều gì. Mẫu này đọc các mẫu ngày càng lớn từ cuối .
Tôi chỉ thấy câu trả lời của Murat Yükselen trong khi viết câu trả lời này. Nó gần giống nhau, mà tôi cho là một điều tốt. Mẫu bên dưới cũng xử lý \ r và tăng bộ đệm của nó ở mỗi bước. Tôi cũng có một số bài kiểm tra đơn vị để sao lưu mã này.
def readlines_reversed(f):
""" Iterate over the lines in a file in reverse. The file must be
open in 'rb' mode. Yields the lines unencoded (as bytes), including the
newline character. Produces the same result as readlines, but reversed.
If this is used to reverse the line in a file twice, the result is
exactly the same.
"""
head = b""
f.seek(0, 2)
t = f.tell()
buffersize, maxbuffersize = 64, 4096
while True:
if t <= 0:
break
# Read next block
buffersize = min(buffersize * 2, maxbuffersize)
tprev = t
t = max(0, t - buffersize)
f.seek(t)
lines = f.read(tprev - t).splitlines(True)
# Align to line breaks
if not lines[-1].endswith((b"\n", b"\r")):
lines[-1] += head # current tail is previous head
elif head == b"\n" and lines[-1].endswith(b"\r"):
lines[-1] += head # Keep \r\n together
elif head:
lines.append(head)
head = lines.pop(0) # can be '\n' (ok)
# Iterate over current block in reverse
for line in reversed(lines):
yield line
if head:
yield head
Đọc từng dòng tệp và sau đó thêm nó vào danh sách theo thứ tự ngược lại.
Đây là một ví dụ về mã:
reverse = []
with open("file.txt", "r") as file:
for line in file:
line = line.strip()
reverse[0:0] = line
import sys
f = open(sys.argv[1] , 'r')
for line in f.readlines()[::-1]:
print line
def previous_line(self, opened_file):
opened_file.seek(0, os.SEEK_END)
position = opened_file.tell()
buffer = bytearray()
while position >= 0:
opened_file.seek(position)
position -= 1
new_byte = opened_file.read(1)
if new_byte == self.NEW_LINE:
parsed_string = buffer.decode()
yield parsed_string
buffer = bytearray()
elif new_byte == self.EMPTY_BYTE:
continue
else:
new_byte_array = bytearray(new_byte)
new_byte_array.extend(buffer)
buffer = new_byte_array
yield None
sử dụng:
opened_file = open(filepath, "rb")
iterator = self.previous_line(opened_file)
line = next(iterator) #one step
close(opened_file)
Tôi đã phải làm điều này một thời gian trước và sử dụng mã dưới đây. Nó ống đến vỏ. Tôi sợ rằng tôi không có kịch bản hoàn chỉnh nữa. Nếu bạn đang sử dụng hệ điều hành unixish, bạn có thể sử dụng "tac", tuy nhiên, ví dụ: lệnh Mac OSX tac không hoạt động, hãy sử dụng đuôi -r. Đoạn mã dưới đây kiểm tra xem bạn đang sử dụng nền tảng nào và điều chỉnh lệnh cho phù hợp
# We need a command to reverse the line order of the file. On Linux this
# is 'tac', on OSX it is 'tail -r'
# 'tac' is not supported on osx, 'tail -r' is not supported on linux.
if sys.platform == "darwin":
command += "|tail -r"
elif sys.platform == "linux2":
command += "|tac"
else:
raise EnvironmentError('Platform %s not supported' % sys.platform)