2009-11-13 4 views
9

Есть ли более короткий (возможно, более питонический) способ открытия текстового файла и чтения за строками, начинающимися с символа комментария?Подробнее pythonic способ пропустить строки заголовка

Других слов, аккуратнее способ сделать это

fin = open("data.txt") 
line = fin.readline() 
while line.startswith("#"): 
    line = fin.readline() 
+0

http://stackoverflow.com/questions/1706198/python-how-to-ignore-comment-lines-when-reading-in-a-file/1706204#1706204 – ghostdog74

+11

«Короче» не обязательно «Pythonic» , То, что у вас есть, очень аккуратно, ясно и само собой разумеется. Свертывание его в неясное однострочное не всегда способствует питоничности. Насколько мне нравится itertools, иногда его функциональный подход заставляет меня перестать почесывать голову. Pythonic-код должен требовать небольшого количества царапин на голове. Если бы мне пришлось голосовать за альтернативную форму и называть ее более Pythonic, это было бы решение о понимании списка Джима Денниса. – PaulMcG

ответ

16

на данном этапе в моей дуге обучения Python, я считаю, это самый Pythonic:

def iscomment(s): 
    return s.startswith('#') 

from itertools import dropwhile 
with open(filename, 'r') as f: 
    for line in dropwhile(iscomment, f): 
     # do something with line 

, чтобы пропустить все строки в верхней части файла, начиная с #. Для того, чтобы пропустить все строки, начинающиеся с #:

from itertools import ifilterfalse 
with open(filename, 'r') as f: 
    for line in ifilterfalse(iscomment, f): 
     # do something with line 

Это почти все о читаемости для меня; функционально нет почти никакой разницы между:

for line in ifilterfalse(iscomment, f)) 

и

for line in (x for x in f if not x.startswith('#')) 

Вырвавшись испытание в своей собственной функции делает цель кода немного яснее; это также означает, что если ваше определение комментария изменится, у вас есть одно место для его изменения.

+0

эти '' '' '' '' '' '' '' '' '' '', да? – Autoplectic

+0

Yikes. Исправлено, спасибо. –

+0

Это работает для Python 2, для Python 3 вы должны использовать 'filterfalse' вместо' ifilterfalse'. – nix

14
for line in open('data.txt'): 
    if line.startswith('#'): 
     continue 
    # work with line 

конечно, если ваши комментируемые линии только в начале файла, вы можете использовать некоторые оптимизации.

+0

+1 Ясный и ясный. Если есть больше условий для фильтрации строк, вы просто добавляете следующую проверку, как это, и она остается чистой. В отличие от укладчатых фильтров. –

6

Если вы хотите, чтобы отфильтровать все строки комментария (не только в начале файла):

for line in file("data.txt"): 
    if not line.startswith("#"): 
    # process line 

Если вы хотите, чтобы пропустить тех, кто в начале, то увидите ephemient «s ответ используя itertools.dropwhile

4

Вы можете сделать генератор, который перебирает файл, который пропускает эти строки:

fin = open("data.txt") 
fileiter = (l for l in fin if not l.startswith('#')) 

for line in fileiter: 
    ... 
5

Вы можете использовать функцию генератора

def readlines(filename): 
    fin = open(filename) 
    for line in fin: 
     if not line.startswith("#"): 
      yield line 

и использовать его как

for line in readlines("data.txt"): 
    # do things 
    pass 

В зависимости от того, где именно файлы берутся, вы также можете strip() линии перед startswith() проверки. Однажды мне пришлось отлаживать скрипт так месяцев после того, как она была написана, потому что кто-то положил в пару символов пробела перед «#»

+1

Это фильтрует все строки, начинающиеся с символа '#', а не только те, что находятся в начале («head») файла - OP не полностью понят на желаемое поведение. – ephemient

+0

Кроме того, вы можете использовать выражение генератора: 'for line in (строка для строки в open ('data.txt'), если не line.startswith ('#')):' – ephemient

+0

См. Мой ответ для версии этого только сбрасывает '#' строки из начала файла, а не из всего файла. – steveha

10
from itertools import dropwhile 
for line in dropwhile(lambda line: line.startswith('#'), file('data.txt')): 
    pass 
2

Вы могли бы сделать что-то вроде

def drop(n, seq): 
    for i, x in enumerate(seq): 
     if i >= n: 
      yield x 

А потом сказать

for line in drop(1, file(filename)): 
    # whatever 
2

Мне нравится идея функции генератора iWerner. Одно небольшое изменение его кода, и он делает то, о чем спрашивал вопрос.

def readlines(filename): 
    f = open(filename) 
    # discard first lines that start with '#' 
    for line in f: 
     if not line.lstrip().startswith("#"): 
      break 
    yield line 

    for line in f: 
     yield line 

и использовать его как

for line in readlines("data.txt"): 
    # do things 
    pass 

Но вот другой подход. Это почти очень просто. Идея состоит в том, что мы открываем файл и получаем файл-объект, который мы можем использовать в качестве итератора. Затем мы вытягиваем строки, которые не хотим выходить из итератора, и просто возвращаем итератор. Это было бы идеально, если бы мы всегда знали, сколько строк пропустить. Проблема здесь в том, что мы не знаем, сколько строк нам нужно пропустить; нам просто нужно тянуть линии и смотреть на них. И нет способа вернуть строку в итератор, как только мы потянем его.

Итак: откройте итератор, вытащите линии и подсчитайте, сколько имеет символ «#»; затем используйте метод .seek() для перемотки назад файла, снова верните правильный номер и верните итератор.

Одна вещь, которая мне нравится в этом вопросе: вы возвращаете фактический объект файла со всеми его методами; вы можете использовать его вместо open(), и он будет работать во всех случаях. Я переименовал функцию в open_my_text(), чтобы отразить это.

def open_my_text(filename): 
    f = open(filename, "rt") 
    # count number of lines that start with '#' 
    count = 0 
    for line in f: 
     if not line.lstrip().startswith("#"): 
      break 
     count += 1 

    # rewind file, and discard lines counted above 
    f.seek(0) 
    for _ in range(count): 
     f.readline() 

    # return file object with comment lines pre-skipped 
    return f 

Вместо f.readline() я мог бы использовать f.next() (для Python 2.x) или next(f) (для Python 3.x), но я хотел написать его так, чтобы было переносимым на любой Python.

EDIT: Хорошо, я знаю, что никто не заботится и я "не получаю никаких upvotes для этого, но я переписал мой ответ в последний раз, чтобы сделать его более изящным

Вы не можете поставить. вернитесь в итератор, но вы можете открыть файл дважды и получить два итератора, учитывая способ работы кэширования файлов, второй итератор почти свободен. Если мы представим файл с мегабайтом строк «#» наверху , эта версия будет значительно превосходить предыдущую версию, которая вызывает f.seek(0).

def open_my_text(filename): 
    # open the same file twice to get two file objects 
    # (We are opening the file read-only so this is safe.) 
    ftemp = open(filename, "rt") 
    f = open(filename, "rt") 

    # use ftemp to look at lines, then discard from f 
    for line in ftemp: 
     if not line.lstrip().startswith("#"): 
      break 
     f.readline() 

    # return file object with comment lines pre-skipped 
    return f 

Эта версия гораздо лучше, чем в предыдущей версии, и она по-прежнему возвращается полный файловый объект со всеми его методами.

+1

Вместо подсчета, почему бы не использовать 'f.tell()' в вашем цикле, чтобы сохранить фактическое место в файле? Замените 'count = 0' на' loc = 0', 'count + = 1' с' loc = f.tell() 'и' f.seek (0) 'с' f.seek (loc) 'и удалите ваш 'for _ in range (count)' loop вообще. – PaulMcG

+0

Мне нравится предложение, но я просто попробовал, и он не работает. Метод '.tell()' не отслеживает итератор; мой короткий тестовый файл полностью закрыт, и '.tell()' возвращал конец файла каждый раз, когда я его вызывал. Если '.tell()' отслеживал итератор, я бы сделал это по-своему; это чище. Мой код беспорядочен, но имеет преимущество фактически работать ... :-) – steveha

5

С практической точки зрения, если бы я знал, что я имел дело с разумными размера текстовых файлов (все, что будет удобно помещается в памяти), то я бы проблема пойти с чем-то вроде:

f = open("data.txt") 
lines = [ x for x in f.readlines() if x[0] != "#" ] 

... перехватить во всем файле и отфильтровать все строки, начинающиеся с октоторпа.

Как уже отмечалось один может понадобиться игнорировать ведущие пробелы, возникающую перед знак числа, как так:

lines = [ x for x in f.readlines() if not x.lstrip().startswith("#") ] 

мне нравится это для своей краткости.

Это предполагает, что мы хотим вычеркнуть все строки комментариев.

Мы также можем «нарезать» последние символы (почти всегда) новая строка с конца каждого использования:

lines = [ x[:-1] for x in ... ] 

... при условии, что мы не обеспокоены позорно неясным вопрос о недостающий final newline в последней строке файла.(Единственный раз, когда строка из .readlines() или связанных с ним файловых методов объекта не может заканчиваться в новой строке, находится в EOF).

В разумных последних версиях Python можно «Chomp» (только Newlines) от концов линий с использованием условного выражения вроде так:

lines = [ x[:-1] if x[-1]=='\n' else x for x in ... ] 

..., который является столь же сложным, как я Пойдем с пониманием списка для удобочитаемости.

Если мы опасались возможности чрезмерно большого файла (или низких ограничений памяти), влияющего на нашу производительность или стабильность, и мы используем версию Python, которая достаточно недавно поддерживает генераторные выражения (которые являются более поздними дополнениями на язык, чем списковый я использую здесь), то мы могли бы использовать:

for line in (x[:-1] if x[-1]=='\n' else x for x in 
    f.readlines() if x.lstrip().startswith('#')): 

    # do stuff with each line 

... находится в пределах того, что я ожидал, что кто-то разобрать в одной строке через год после код был проверен.

Если целью является только пропустить «заголовок», то я думаю, что наилучший подход be:

f = open('data.txt') 
for line in f: 
    if line.lstrip().startswith('#'): 
     continue 

... и сделайте это.

Смежные вопросы