Як прочитати одночасно два рядки з файлу за допомогою python


82

Я кодую скрипт python, який аналізує текстовий файл. Формат цього текстового файлу такий, що кожен елемент у файлі використовує два рядки, і для зручності я хотів би прочитати обидва рядки перед розбором. Чи можна це зробити в Python?

Я хотів би щось на зразок:

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

f.close

Але це розбиває твердження, що:

ValueError: Змішування методів ітерації та читання втратить дані

Пов’язані:


8
Змініть f.readline () на f.next (), і все готово.
Пол

Дивіться stackoverflow.com/questions/1528711/reading-lines-2-at-a-time для отримання додаткових відповідей.
foosion

@Paul Чи є цей f.next () все ще дійсним? Я отримую цю помилку AttributeError: '_io.TextIOWrapper' об'єкт не має атрибута 'next'
SKR

1
@SKR на Python 3 next(f)замість цього потрібно зробити .
Борис

Відповіді:


50

Подібне питання тут . Ви не можете поєднувати ітерацію та лінію зчитування, тому вам потрібно використовувати ту чи іншу.

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() повертає ітератор, тому він буде добре працювати, навіть якщо файл має мільярди рядків.

Якщо існує непарна кількість рядків, то line2для Noneостанньої ітерації встановлюється значення.

Натомість на Python2 вам потрібно використовувати izip_longest.


У коментарях запитували, чи це рішення спочатку зчитує весь файл, а потім повторює файл вдруге. Я вважаю, що це не так. with open('a') as fЛінія відкриває дескриптор файлу, але не читає файл. fє ітератором, тому його вміст не читається до запиту. zip_longestприймає ітератори в якості аргументів і повертає ітератор.

zip_longestдійсно подається одним і тим же ітератором, f, двічі. Але в підсумку відбувається те, що next(f)викликається на першому аргументі, а потім на другому аргументі. Оскільки next()викликається на одному базовому ітераторі, виходять послідовні рядки. Це сильно відрізняється від читання у цілому файлі. Дійсно, метою використання ітераторів є саме уникнення читання у цілому файлі.

Тому я вважаю, що рішення працює за бажанням - файл читає цикл for лише один раз.

Щоб підтвердити це, я запустив рішення zip_longest проти рішення, що використовує f.readlines(). Я поставив в input()кінці, щоб призупинити сценарії, і побіг ps axuwпо кожному:

% 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

readlinesЧітко читає весь файл відразу. Оскільки zip_longest_methodвикористовує набагато менше пам'яті, я думаю, можна з упевненістю зробити висновок, що це не читання всього файлу одночасно.


6
Мені подобається, (*[f]*2)оскільки це показує, що ви можете отримати будь-які фрагменти, які хочете, просто змінивши номер (тому я не буду редагувати відповідь, щоб змінити його), але в цьому випадку (f, f), напевно, легше набирати текст.
Стів Лош,

якщо ви використовуєте linesзамість line1, line2цього, вам просто потрібно змінити одне число ( 2) для читання nрядків за раз.
jfs

27

використовувати next(), напр

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

1
як зазначає RedGlyph у своїй версії цієї відповіді, непарна кількість рядків призведе до StopIterationпідвищення.
drevicko

2
next () nows підтримує аргумент за замовчуванням, щоб уникнути винятку:nextline = next(f,None)
gerardw

11

Я діяв би так само, як ghostdog74 , лише із спробою назовні та кількома модифікаціями:

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

Це робить код простим, але надійним. Використання withзакриває файл, якщо трапляється щось інше, або просто закриває ресурси після того, як ви його вичерпали та вийдете з циклу.

Зверніть увагу, що withпотрібно 2.6 або 2.5 з with_statementувімкненою функцією.


8

як щодо цього, хтось бачить з ним проблему

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

1
Це відкине останній рядок, якщо у вашому файлі буде непарна кількість рядків. Приємно те, що ви можете продовжити це, щоб прочитати 3 рядки одночасно за допомогою for l1, l2, l3 in zip(f, f, f):тощо; знову ж, останні 1 або 2 рядки будуть відкинуті, якщо кількість рядків не ділиться на 3.
Борис

4

Працює для файлів з парною та непарною довжиною. Він просто ігнорує неперевершений останній рядок.

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()

Якщо у вас великі файли, це не правильний підхід. Ви завантажуєте весь файл в пам'ять за допомогою readlines (). Одного разу я написав клас, який читав файл, зберігаючи позицію fseek кожного початку рядка. Це дозволяє отримувати певні рядки, не маючи всього файлу в пам'яті, а також можна рухатись вперед і назад.

Я вставляю його сюди. Ліцензія - це суспільне надбання, тобто робіть із нею все, що хочете. Зверніть увагу, що цей клас написаний 6 років тому, і я з того часу його не торкався і не перевіряв. Я думаю, це навіть не відповідає файлам. Застереження emptor . Також зауважте, що це надмірно для вашої проблеми. Я не стверджую, що ви точно повинні піти цим шляхом, але у мене був цей код, і я із задоволенням ділюсь ним, якщо вам потрібен більш складний доступ.

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

Можливо, ви захочете використовувати itertools.izip () замість цього, особливо для великих файлів!
RedGlyph

Навіть із izip, нарізання списку таким чином потягне все в пам’ять.
Стів Лош,

Насправді readlines()дзвінок також усе втягне в пам'ять.
Стів Лош,

Мені не подобається ваш клас. Ви повторюєте два рази по всьому файлу під час ініціалізації файлу. Для великих файлів із короткими рядками збереженої пам’яті мало.
Георг Шеллі,

@ Steve: так, на жаль. Але zip додасть додатковий шар в пам'ять, створивши цілий список кортежів (якщо це не Python 3), де izip генерує кортежі по одному. Думаю, це саме те, що ви мали на увазі, але я все-таки краще пояснитиму свій попередній коментар :-)
RedGlyph

3
file_name = 'ваше_файл_назви'
file_open = відкрити (ім'я_файлу, 'r')

обробник def (line_one, line_two):
    друк (line_one, line_two)

while file_open:
    спробуйте:
        one = file_open.next ()
        два = file_open.next () 
        обробник (один, два)
    крім (StopIteration):
        file_open.close ()
        перерву

1
while file_open:вводить в оману через те, що while True:в цьому випадку еквівалентно .
jfs

Що навмисно, хоча я погоджуюсь, що чистішим є численніший «чистіший», вказуючи, що вам потрібна перерва, щоб вийти з циклу. Я вирішив цього не робити, тому що вважаю (знову ж таки сперечається), що це так приємніше читається, не залишаючи сумнівів у тому, як довго файл повинен залишатися відкритим, і що робити з ним тим часом. Більшу частину часу я хотів би робити `` правду '' для себе.
Martin P. Hellwig 02

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

Моя ідея полягає в тому, щоб створити генератор, який одночасно зчитує два рядки з файлу і повертає це як 2-кортеж, Це означає, що ви можете потім переглядати результати.

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

Якщо у вас непарна кількість рядків, це не буде працювати ідеально, але це має дати вам хороший контур.


1

Я працював над подібною проблемою минулого місяця. Я спробував цикл while за допомогою f.readline (), а також f.readlines (). Мій файл даних не є величезним, тому я нарешті вибрав f.readlines (), що дає мені більше контролю над індексом, інакше я повинен використовувати f.seek () для переміщення вперед і назад вказівника на файл.

Моя справа складніша, ніж OP. Оскільки мій файл даних є більш гнучким щодо того, скільки рядків слід аналізувати щоразу, тому мені доводиться перевіряти кілька умов, перш ніж я зможу проаналізувати дані.

Ще одна проблема, яку я дізнався про f.seek (), полягає в тому, що він не дуже добре обробляє utf-8, коли я використовую codecs.open ('', 'r', 'utf-8'), (не зовсім впевнений у винуватець, врешті-решт я відмовився від цього підходу.)


1

Простий маленький читач. Він буде тягнути лінії парами по два і повертати їх у вигляді кортежу під час ітерації по об’єкту. Ви можете закрити його вручну, або він закриється, коли він вийде за межі сфери дії.

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

Зараз ви можете читати файл кожні два рядки. Якщо вам подобається, ви також можете перевірити статус f ранішеf.next()


0

Якщо файл має розумний розмір, іншим підходом, який використовує розуміння списку для зчитування всього файлу в список із двох кортежів , є такий:

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

Цей код Python надрукує перші два рядки:

import linecache  
filename = "ooxx.txt"  
print(linecache.getline(filename,2))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.