Làm cách nào để đọc hai dòng từ một tệp cùng một lúc bằng python


82

Tôi đang mã hóa một tập lệnh python phân tích cú pháp một tệp văn bản. Định dạng của tệp văn bản này sao cho mỗi phần tử trong tệp sử dụng hai dòng và để thuận tiện, tôi muốn đọc cả hai dòng trước khi phân tích cú pháp. Điều này có thể được thực hiện bằng Python không?

Tôi muốn một số thứ như:

f = open(filename, "r")
for line in f:
    line1 = line
    line2 = f.readline()

f.close

Nhưng điều này phá vỡ nói rằng:

ValueError: Trộn các phương pháp lặp và đọc sẽ làm mất dữ liệu

Có liên quan:


8
Thay đổi f.readline () thành f.next () và bạn đã hoàn tất.
Paul

Xem stackoverflow.com/questions/1528711/reading-lines-2-at-a-time để biết thêm một số câu trả lời.
foosion

@Paul Liệu f.next () này có còn hợp lệ không? Tôi nhận được AttributeError lỗi này: đối tượng '_io.TextIOWrapper' không có thuộc tính 'bên cạnh'
SKR

1
Bạn phải thực hiện @SKR trên Python 3 next(f).
Boris

Câu trả lời:


50

Câu hỏi tương tự ở đây . Bạn không thể kết hợp phép lặp và dòng đọc, vì vậy bạn cần sử dụng cái này hay cái kia.

while True:
    line1 = f.readline()
    line2 = f.readline()
    if not line2: break  # EOF
    ...

48
import itertools
with open('a') as f:
    for line1,line2 in itertools.zip_longest(*[f]*2):
        print(line1,line2)

itertools.zip_longest() trả về một trình lặp, vì vậy nó sẽ hoạt động tốt ngay cả khi tệp dài hàng tỷ dòng.

Nếu có một số dòng lẻ, thì line2được đặt Noneở lần lặp cuối cùng.

Trên Python2, bạn cần sử dụng izip_longestthay thế.


Trong các nhận xét, người ta đã hỏi liệu giải pháp này có đọc toàn bộ tệp trước hay không, sau đó lặp lại tệp lần thứ hai. Tôi tin rằng nó không. Các with open('a') as fdòng mở một tập tin xử lý, nhưng không đọc các tập tin. flà một trình lặp, vì vậy nội dung của nó không được đọc cho đến khi được yêu cầu. zip_longestlấy trình vòng lặp làm đối số và trả về một trình vòng lặp.

zip_longestthực sự được cung cấp cùng một trình lặp, f, hai lần. Nhưng những gì cuối cùng xảy ra là điều đó next(f)được gọi trên đối số đầu tiên và sau đó là đối số thứ hai. Vì next()đang được gọi trên cùng một trình lặp bên dưới, các dòng liên tiếp được tạo ra. Điều này rất khác so với việc đọc toàn bộ tệp. Thật vậy, mục đích của việc sử dụng trình lặp chính xác là để tránh việc đọc toàn bộ tệp.

Do đó, tôi tin rằng giải pháp hoạt động như mong muốn - tệp chỉ được đọc một lần bởi vòng lặp for.

Để chứng thực điều này, tôi đã chạy giải pháp zip_longest so với giải pháp sử dụng f.readlines(). Tôi đặt dấu chấm input()ở cuối để tạm dừng các tập lệnh và chạy ps axuwtrên từng:

% ps axuw | grep zip_longest_method.py

unutbu 11119 2.2 0.2 4520 2712 pts/0 S+ 21:14 0:00 python /home/unutbu/pybin/zip_longest_method.py bigfile

% ps axuw | grep readlines_method.py

unutbu 11317 6.5 8.8 93908 91680 pts/0 S+ 21:16 0:00 python /home/unutbu/pybin/readlines_method.py bigfile

Các readlineslần đọc rõ trong toàn bộ tập tin cùng một lúc. Vì zip_longest_methodbộ nhớ sử dụng ít hơn nhiều, tôi nghĩ rằng có thể an toàn khi kết luận rằng nó không đọc toàn bộ tệp cùng một lúc.


6
Tôi thích (*[f]*2)nó vì nó cho thấy rằng bạn có thể nhận được bất kỳ kích thước nào bạn muốn chỉ bằng cách thay đổi số (vì vậy tôi sẽ không chỉnh sửa câu trả lời để thay đổi nó), nhưng trong trường hợp (f, f)này có lẽ dễ nhập hơn.
Steve Losh

nếu bạn sử dụng linesthay vì line1, line2thì bạn chỉ cần thay đổi một số ( 2) để đọc ncác dòng tại một thời điểm.
jfs

27

sử dụng next(), ví dụ

with open("file") as f:
    for line in f:
        print(line)
        nextline = next(f)
        print("next line", nextline)
        ....

1
như RedGlyph đã chỉ ra trong phiên bản câu trả lời này của mình, một số dòng lẻ sẽ dẫn đến StopIterationviệc được nâng lên.
drevicko

2
next () hiện hỗ trợ một đối số mặc định để tránh ngoại lệ:nextline = next(f,None)
gerardw

11

Tôi sẽ tiến hành theo cách tương tự như ghostdog74 , chỉ với việc thử bên ngoài và một vài sửa đổi:

try:
    with open(filename) as f:
        for line1 in f:
            line2 = f.next()
            # process line1 and line2 here
except StopIteration:
    print "(End)" # do whatever you need to do with line1 alone

Điều này giúp mã đơn giản và mạnh mẽ. Việc sử dụng sẽ withđóng tệp nếu có điều gì khác xảy ra hoặc chỉ đóng tài nguyên khi bạn đã sử dụng hết và thoát khỏi vòng lặp.

Lưu ý rằng withcần 2,6 hoặc 2,5 với with_statementtính năng được bật.


8

còn cái này thì sao, có ai thấy vấn đề với nó không

with open('file_name') as f:
    for line1, line2 in zip(f, f):
        print(line1, line2)

1
Thao tác này sẽ loại bỏ dòng cuối cùng nếu tệp của bạn có số dòng lẻ. Điều thú vị là bạn có thể mở rộng điều này để đọc 3 dòng cùng một lúc for l1, l2, l3 in zip(f, f, f):, v.v. một lần nữa, 1 hoặc 2 dòng cuối cùng sẽ bị hủy nếu số dòng là không chia hết cho 3.
Boris

4

Hoạt động cho các tệp có độ dài chẵn và lẻ. Nó chỉ bỏ qua dòng cuối cùng chưa được so sánh.

f=file("file")

lines = f.readlines()
for even, odd in zip(lines[0::2], lines[1::2]):
    print "even : ", even
    print "odd : ", odd
    print "end cycle"
f.close()

Nếu bạn có các tệp lớn, đây không phải là cách tiếp cận chính xác. Bạn đang tải tất cả tệp trong bộ nhớ với các dòng đọc (). Tôi đã từng viết một lớp đọc tệp lưu vị trí fseek của mỗi đầu dòng. Điều này cho phép bạn có được các dòng cụ thể mà không cần có tất cả tệp trong bộ nhớ và bạn cũng có thể tiến và lùi.

Tôi dán nó vào đây. Giấy phép là phạm vi công cộng, có nghĩa là, làm những gì bạn muốn với nó. Xin lưu ý rằng lớp học này đã được viết cách đây 6 năm và tôi đã không chạm vào hoặc kiểm tra nó kể từ đó. Tôi nghĩ rằng nó thậm chí không tuân thủ tệp. Dấu hiệu báo trước . Ngoài ra, lưu ý rằng điều này là quá mức cần thiết cho vấn đề của bạn. Tôi không khẳng định bạn chắc chắn nên đi theo cách này, nhưng tôi đã có mã này và tôi muốn chia sẻ nó nếu bạn cần truy cập phức tạp hơn.

import string
import re

class FileReader:
    """ 
    Similar to file class, but allows to access smoothly the lines 
    as when using readlines(), with no memory payload, going back and forth,
    finding regexps and so on.
    """
    def __init__(self,filename): # fold>>
        self.__file=file(filename,"r")
        self.__currentPos=-1
        # get file length
        self.__file.seek(0,0)
        counter=0
        line=self.__file.readline()
        while line != '':
            counter = counter + 1
            line=self.__file.readline()
        self.__length = counter
        # collect an index of filedescriptor positions against
        # the line number, to enhance search
        self.__file.seek(0,0)
        self.__lineToFseek = []

        while True:
            cur=self.__file.tell()
            line=self.__file.readline()
            # if it's not null the cur is valid for
            # identifying a line, so store
            self.__lineToFseek.append(cur)
            if line == '':
                break
    # <<fold
    def __len__(self): # fold>>
        """
        member function for the operator len()
        returns the file length
        FIXME: better get it once when opening file
        """
        return self.__length
        # <<fold
    def __getitem__(self,key): # fold>>
        """ 
        gives the "key" line. The syntax is

        import FileReader
        f=FileReader.FileReader("a_file")
        line=f[2]

        to get the second line from the file. The internal
        pointer is set to the key line
        """

        mylen = self.__len__()
        if key < 0:
            self.__currentPos = -1
            return ''
        elif key > mylen:
            self.__currentPos = mylen
            return ''

        self.__file.seek(self.__lineToFseek[key],0)
        counter=0
        line = self.__file.readline()
        self.__currentPos = key
        return line
        # <<fold
    def next(self): # fold>>
        if self.isAtEOF():
            raise StopIteration
        return self.readline()
    # <<fold
    def __iter__(self): # fold>>
        return self
    # <<fold
    def readline(self): # fold>>
        """
        read a line forward from the current cursor position.
        returns the line or an empty string when at EOF
        """
        return self.__getitem__(self.__currentPos+1)
        # <<fold
    def readbackline(self): # fold>>
        """
        read a line backward from the current cursor position.
        returns the line or an empty string when at Beginning of
        file.
        """
        return self.__getitem__(self.__currentPos-1)
        # <<fold
    def currentLine(self): # fold>>
        """
        gives the line at the current cursor position
        """
        return self.__getitem__(self.__currentPos)
        # <<fold
    def currentPos(self): # fold>>
        """ 
        return the current position (line) in the file
        or -1 if the cursor is at the beginning of the file
        or len(self) if it's at the end of file
        """
        return self.__currentPos
        # <<fold
    def toBOF(self): # fold>>
        """
        go to beginning of file
        """
        self.__getitem__(-1)
        # <<fold
    def toEOF(self): # fold>>
        """
        go to end of file
        """
        self.__getitem__(self.__len__())
        # <<fold
    def toPos(self,key): # fold>>
        """
        go to the specified line
        """
        self.__getitem__(key)
        # <<fold
    def isAtEOF(self): # fold>>
        return self.__currentPos == self.__len__()
        # <<fold
    def isAtBOF(self): # fold>>
        return self.__currentPos == -1
        # <<fold
    def isAtPos(self,key): # fold>>
        return self.__currentPos == key
        # <<fold

    def findString(self, thestring, count=1, backward=0): # fold>>
        """
        find the count occurrence of the string str in the file
        and return the line catched. The internal cursor is placed
        at the same line.
        backward is the searching flow.
        For example, to search for the first occurrence of "hello
        starting from the beginning of the file do:

        import FileReader
        f=FileReader.FileReader("a_file")
        f.toBOF()
        f.findString("hello",1,0)

        To search the second occurrence string from the end of the
        file in backward movement do:

        f.toEOF()
        f.findString("hello",2,1)

        to search the first occurrence from a given (or current) position
        say line 150, going forward in the file 

        f.toPos(150)
        f.findString("hello",1,0)

        return the string where the occurrence is found, or an empty string
        if nothing is found. The internal counter is placed at the corresponding
        line number, if the string was found. In other case, it's set at BOF
        if the search was backward, and at EOF if the search was forward.

        NB: the current line is never evaluated. This is a feature, since
        we can so traverse occurrences with a

        line=f.findString("hello")
        while line == '':
            line.findString("hello")

        instead of playing with a readline every time to skip the current
        line.
        """
        internalcounter=1
        if count < 1:
            count = 1
        while 1:
            if backward == 0:
                line=self.readline()
            else:
                line=self.readbackline()

            if line == '':
                return ''
            if string.find(line,thestring) != -1 :
                if count == internalcounter:
                    return line
                else:
                    internalcounter = internalcounter + 1
                    # <<fold
    def findRegexp(self, theregexp, count=1, backward=0): # fold>>
        """
        find the count occurrence of the regexp in the file
        and return the line catched. The internal cursor is placed
        at the same line.
        backward is the searching flow.
        You need to pass a regexp string as theregexp.
        returns a tuple. The fist element is the matched line. The subsequent elements
        contains the matched groups, if any.
        If no match returns None
        """
        rx=re.compile(theregexp)
        internalcounter=1
        if count < 1:
            count = 1
        while 1:
            if backward == 0:
                line=self.readline()
            else:
                line=self.readbackline()

            if line == '':
                return None
            m=rx.search(line)
            if m != None :
                if count == internalcounter:
                    return (line,)+m.groups()
                else:
                    internalcounter = internalcounter + 1
    # <<fold
    def skipLines(self,key): # fold>>
        """
        skip a given number of lines. Key can be negative to skip
        backward. Return the last line read.
        Please note that skipLines(1) is equivalent to readline()
        skipLines(-1) is equivalent to readbackline() and skipLines(0)
        is equivalent to currentLine()
        """
        return self.__getitem__(self.__currentPos+key)
    # <<fold
    def occurrences(self,thestring,backward=0): # fold>>
        """
        count how many occurrences of str are found from the current
        position (current line excluded... see skipLines()) to the
        begin (or end) of file.
        returns a list of positions where each occurrence is found,
        in the same order found reading the file.
        Leaves unaltered the cursor position.
        """
        curpos=self.currentPos()
        list = []
        line = self.findString(thestring,1,backward)
        while line != '':
            list.append(self.currentPos())
            line = self.findString(thestring,1,backward)
        self.toPos(curpos)
        return list
        # <<fold
    def close(self): # fold>>
        self.__file.close()
    # <<fold

Bạn có thể muốn sử dụng itertools.izip (), đặc biệt là đối với các tệp lớn!
RedGlyph

Ngay cả với izip, việc cắt danh sách như vậy sẽ kéo mọi thứ vào bộ nhớ.
Steve Losh

Thực ra readlines()cuộc gọi cũng sẽ kéo mọi thứ vào bộ nhớ.
Steve Losh

Tôi không thích lớp học của bạn. Bạn đang lặp lại hai lần trên toàn bộ tệp trong khi khởi tạo tệp. Đối với các tệp lớn có dòng ngắn, bộ nhớ lưu không nhiều.
Georg Schölly

@Steve: vâng, thật đáng buồn. Nhưng zip sẽ thêm một lớp bổ sung vào bộ nhớ bằng cách tạo toàn bộ danh sách các bộ giá trị (trừ khi đó là Python 3), trong đó izip sẽ tạo các bộ giá trị một lần. Tôi nghĩ rằng đó là những gì bạn có nghĩa là, nhưng tôi thà làm rõ bình luận trước đây của tôi anyway :-)
RedGlyph

3
file_name = 'your_file_name'
file_open = open (file_name, 'r')

trình xử lý def (line_one, line_two):
    print (line_one, line_two)

trong khi tệp_open:
    thử:
        one = file_open.next ()
        hai = file_open.next () 
        xử lý (một, hai)
    ngoại trừ (StopIteration):
        file_open.close ()
        phá vỡ

1
while file_open:là sai lệch do nó tương đương với while True:trong trường hợp này.
jfs

Đó là cố ý, mặc dù tôi đồng ý rằng nó được cho là sạch hơn để làm 'trong khi True' cho thấy rằng bạn cần nghỉ ngơi để thoát ra khỏi vòng lặp. Tôi đã chọn không làm điều đó vì tôi tin rằng (một lần nữa có thể tranh luận) rằng nó đọc đẹp hơn theo cách này, không để lại nghi ngờ gì về việc tệp cần duy trì mở trong bao lâu và phải làm gì với nó trong thời gian ngắn. Hầu hết thời gian tôi cũng làm 'trong khi Đúng' cho bản thân.
Martin P. Hellwig

2
def readnumlines(file, num=2):
    f = iter(file)
    while True:
        lines = [None] * num
        for i in range(num):
            try:
                lines[i] = f.next()
            except StopIteration: # EOF or not enough lines available
                return
        yield lines

# use like this
f = open("thefile.txt", "r")
for line1, line2 in readnumlines(f):
    # do something with line1 and line2

# or
for line1, line2, line3, ..., lineN in readnumlines(f, N):
    # do something with N lines

1

Ý tưởng của tôi là tạo một trình tạo đọc hai dòng từ tệp cùng một lúc và trả về giá trị này dưới dạng 2-tuple, Điều này có nghĩa là sau đó bạn có thể lặp lại các kết quả.

from cStringIO import StringIO

def read_2_lines(src):   
    while True:
        line1 = src.readline()
        if not line1: break
        line2 = src.readline()
        if not line2: break
        yield (line1, line2)


data = StringIO("line1\nline2\nline3\nline4\n")
for read in read_2_lines(data):
    print read

Nếu bạn có một số dòng lẻ, nó sẽ không hoạt động hoàn hảo, nhưng điều này sẽ cung cấp cho bạn một dàn bài tốt.


1

Tôi đã làm việc với một vấn đề tương tự vào tháng trước. Tôi đã thử một vòng lặp while với f.readline () cũng như f.readlines (). Tệp dữ liệu của tôi không lớn, vì vậy cuối cùng tôi đã chọn f.readlines (), điều này cho phép tôi kiểm soát chỉ mục nhiều hơn, nếu không, tôi phải sử dụng f.seek () để di chuyển qua lại con trỏ tệp.

Trường hợp của tôi phức tạp hơn OP. Bởi vì tệp dữ liệu của tôi linh hoạt hơn về số dòng được phân tích cú pháp mỗi lần, vì vậy tôi phải kiểm tra một vài điều kiện trước khi có thể phân tích cú pháp dữ liệu.

Một vấn đề khác mà tôi phát hiện ra về f.seek () là nó không xử lý utf-8 rất tốt khi tôi sử dụng codecs.open ('', 'r', 'utf-8'), (không chắc chắn lắm về thủ phạm, cuối cùng tôi đã từ bỏ cách tiếp cận này.)


1

Người đọc ít đơn giản. Nó sẽ kéo các dòng theo cặp hai và trả về chúng dưới dạng một bộ khi bạn lặp qua đối tượng. Bạn có thể đóng nó theo cách thủ công hoặc nó sẽ tự đóng khi nó nằm ngoài phạm vi.

class doublereader:
    def __init__(self,filename):
        self.f = open(filename, 'r')
    def __iter__(self):
        return self
    def next(self):
        return self.f.next(), self.f.next()
    def close(self):
        if not self.f.closed:
            self.f.close()
    def __del__(self):
        self.close()

#example usage one
r = doublereader(r"C:\file.txt")
for a, h in r:
    print "x:%s\ny:%s" % (a,h)
r.close()

#example usage two
for x,y in doublereader(r"C:\file.txt"):
    print "x:%s\ny:%s" % (x,y)
#closes itself as soon as the loop goes out of scope

1
f = open(filename, "r")
for line in f:
    line1 = line
    f.next()

f.close

Ngay bây giờ, bạn có thể đọc tệp hai dòng một lần. Nếu bạn thích, bạn cũng có thể kiểm tra trạng thái f trước khif.next()


0

Nếu tệp có kích thước hợp lý, một cách tiếp cận khác sử dụng khả năng hiểu danh sách để đọc toàn bộ tệp thành danh sách gồm 2 bộ , là:

filaname = '/path/to/file/name'

with open(filename, 'r') as f:
    list_of_2tuples = [ (line,f.readline()) for line in f ]

for (line1,line2) in list_of_2tuples: # Work with them in pairs.
    print('%s :: %s', (line1,line2))

-2

Mã Python này sẽ in hai dòng đầu tiên:

import linecache  
filename = "ooxx.txt"  
print(linecache.getline(filename,2))
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.