Python: как игнорировать строки #comment при чтении в файле - PullRequest
40 голосов
/ 10 ноября 2009

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

Я думаю, что это должно быть примерно так:

for 
   if line !contain #
      then ...process line
   else end for loop 

Но я новичок в Python и не знаю синтаксис

Ответы [ 9 ]

53 голосов
/ 10 ноября 2009

вы можете использовать начинается с ()

например

for line in open("file"):
    li=line.strip()
    if not li.startswith("#"):
        print line.rstrip()
41 голосов
/ 10 ноября 2009

Я рекомендую не игнорировать всю строку, когда вы видите символ #; просто игнорируйте остальную часть строки. Это легко сделать с помощью функции строкового метода с именем partition:

with open("filename") as f:
    for line in f:
        line = line.partition('#')[0]
        line = line.rstrip()
        # ... do something with line ...

partition возвращает кортеж: все до строки раздела, строки раздела и все после строки раздела. Итак, индексируя с помощью [0], мы берем только часть перед строкой раздела.

EDIT: Если вы используете версию Python, в которой нет partition(), вот код, который вы можете использовать:

with open("filename") as f:
    for line in f:
        line = line.split('#', 1)[0]
        line = line.rstrip()
        # ... do something with line ...

Это разбивает строку на символ «#», а затем сохраняет все до разделения. Аргумент 1 останавливает метод .split() после одного разбиения; поскольку мы просто берем 0-ю подстроку (с помощью индексации с помощью [0]), вы получите тот же ответ без аргумента 1, но это может быть немного быстрее. (Упрощено из моего исходного кода благодаря комментарию от @gnr. Мой оригинальный код был беспорядочным без всякой причины; спасибо, @gnr.)

Вы также можете написать свою собственную версию partition(). Вот тот, который называется part():

def part(s, s_part):
    i0 = s.find(s_part)
    i1 = i0 + len(s_part)
    return (s[:i0], s[i0:i1], s[i1:])

@ dalle отметил, что «#» может появиться внутри строки. Нелегко правильно разобраться с этим делом, поэтому я просто проигнорировал это, но я должен был что-то сказать.

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

Но если мы ограничимся простой строкой в ​​кавычках, мы можем обработать ее с помощью простого конечного автомата. Мы можем даже разрешить использовать двойные кавычки с обратной косой чертой внутри строки.

c_backslash = '\\'
c_dquote = '"'
c_comment = '#'


def chop_comment(line):
    # a little state machine with two state varaibles:
    in_quote = False  # whether we are in a quoted string right now
    backslash_escape = False  # true if we just saw a backslash

    for i, ch in enumerate(line):
        if not in_quote and ch == c_comment:
            # not in a quote, saw a '#', it's a comment.  Chop it and return!
            return line[:i]
        elif backslash_escape:
            # we must have just seen a backslash; reset that flag and continue
            backslash_escape = False
        elif in_quote and ch == c_backslash:
            # we are in a quote and we see a backslash; escape next char
            backslash_escape = True
        elif ch == c_dquote:
            in_quote = not in_quote

    return line

Я действительно не хотел усложнять вопрос с меткой «новичок», но этот конечный автомат достаточно прост, и я надеюсь, что он будет интересным.

8 голосов
/ 28 ноября 2014

Я опаздываю, но проблема обработки стиля оболочки (или стиля python) # комментариев является очень распространенной.

Я использую код почти каждый раз, когда читаю текстовый файл.
Проблема в том, что он неправильно обрабатывает цитируемые или экранированные комментарии . Но это работает для простых случаев и легко.

for line in whatever:
    line = line.split('#',1)[0].strip()
    if not line:
        continue
    # process line

Более надежное решение - использовать shlex :

import shlex
for line in instream:
    lex = shlex.shlex(line)
    lex.whitespace = '' # if you want to strip newlines, use '\n'
    line = ''.join(list(lex))
    if not line:
        continue
    # process decommented line

Этот подход shlex не только правильно обрабатывает кавычки и экранирует его, он добавляет множество интересных функций (например, возможность получать файлы из других файлов, если хотите) Я не проверял его на скорость на больших файлах, но он достаточно быстр для мелких вещей.

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

import shlex
for line in instream:
    fields = shlex.split(line, comments=True)
    if not fields:
        continue
    # process list of fields 
6 голосов
/ 11 ноября 2009

Это самая короткая форма:

for line in open(filename):
  if line.startswith('#'):
    continue
  # PROCESS LINE HERE

Метод startswith() в строке возвращает True, если строка, в которой вы вызываете его, начинается со строки, которую вы передали.

Хотя в некоторых случаях, например, в сценариях оболочки, это нормально, у него две проблемы. Во-первых, он не определяет, как открыть файл. Режим открытия файла по умолчанию - 'r', что означает «чтение файла в двоичном режиме». Поскольку вы ожидаете текстовый файл, лучше открыть его с помощью 'rt'. Хотя это различие не имеет отношения к UNIX-подобным операционным системам, оно важно для Windows (и для Mac с пред-OS X).

Вторая проблема - дескриптор открытого файла. Функция open() возвращает файловый объект, и рекомендуется закрывать файлы, когда вы закончите с ними. Для этого вызовите метод close() объекта. Теперь Python , вероятно, сделает это за вас, в конце концов; в объектах Python подсчитывается по ссылкам, а когда счетчик ссылок объекта обнуляется, он освобождается, и в некоторый момент после объект освобожден Python вызовет свой деструктор (специальный метод __del__). Обратите внимание, что я сказал , вероятно: У Python плохая привычка не вызывать деструктор для объектов, число ссылок которых падает до нуля незадолго до завершения программы. Я думаю, это спешит!

Для недолговечных программ, таких как сценарии оболочки, и особенно для файловых объектов, это не имеет значения. Ваша операционная система автоматически очистит все дескрипторы файлов, оставленные открытыми, когда программа завершится. Но если вы открыли файл, прочитали его содержимое, а затем начали длинное вычисление без явного закрытия дескриптора файла, Python, вероятно, оставит дескриптор файла открытым во время ваших вычислений. И это плохая практика.

Эта версия будет работать в любой версии 2.x Python и исправляет обе проблемы, которые я обсуждал выше:

f = open(file, 'rt')
for line in f:
  if line.startswith('#'):
    continue
  # PROCESS LINE HERE
f.close()

Это лучшая общая форма для старых версий Python.

Как предполагает Стивеха, использование выражения «с» теперь считается наилучшей практикой. Если вы используете 2.6 или выше, вы должны написать это так:

with open(filename, 'rt') as f:
  for line in f:
    if line.startswith('#'):
      continue
    # PROCESS LINE HERE

Оператор "with" очистит дескриптор файла для вас.

В своем вопросе вы сказали «строки, начинающиеся с #», вот что я вам здесь показал. Если вы хотите отфильтровать строки, начинающиеся с необязательных пробелов и , а затем с '#', вы должны удалить пробелы перед поиском '#'. В этом случае вы должны изменить это:

    if line.startswith('#'):

к этому:

    if line.lstrip().startswith('#'):

В Python строки являются неизменяемыми, поэтому это не меняет значение line. Метод lstrip() возвращает копию строки со всеми удаленными начальными пробелами.

5 голосов
/ 21 июня 2012

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

Я определяю свою функцию как

def skip_comments(file):
    for line in file:
        if not line.strip().startswith('#'):
            yield line

Таким образом, я могу просто сделать

f = open('testfile')
for line in skip_comments(f):
    print line

Это многократно используется во всем моем коде, и я могу добавить любую дополнительную обработку / ведение журнала / и т.д. что мне нужно.

3 голосов
/ 09 марта 2015

Я знаю, что это старый поток, но это функция генератора, которую я использовать для моих собственных целей. Это лишает комментариев независимо от того, где они появляются в строке, а также удаляя начальные / конечные пробелы и пустые строки. Следующий исходный текст:

# Comment line 1
# Comment line 2

# host01  # This host commented out.
host02  # This host not commented out.
host03
  host04  # Oops! Included leading whitespace in error!

даст:

host02
host03
host04

Вот документированный код, который включает демонстрацию:

def strip_comments(item, *, token='#'):
    """Generator. Strips comments and whitespace from input lines.

    This generator strips comments, leading/trailing whitespace, and
    blank lines from its input.

    Arguments:
        item (obj):  Object to strip comments from.
        token (str, optional):  Comment delimiter.  Defaults to ``#``.

    Yields:
        str:  Next non-blank line from ``item`` with comments and
            leading/trailing whitespace removed.

    """

    for line in item:
        s = line.split(token, 1)[0].strip()
        if s != '':
            yield s


if __name__ == '__main__':
    HOSTS = """# Comment line 1
    # Comment line 2

    # host01  # This host commented out.
    host02  # This host not commented out.
    host03
      host04  # Oops! Included leading whitespace in error!""".split('\n')


    hosts = strip_comments(HOSTS)
    print('\n'.join(h for h in hosts))

Обычным вариантом использования будет удаление комментариев из файла (то есть файла hosts, как в моем примере выше). Если это так, то конец приведенного выше кода будет изменен на:

if __name__ == '__main__':
    with open('hosts.txt', 'r') as f:
        hosts = strip_comments(f)

    for host in hosts:
        print('\'%s\'' % host)
3 голосов
/ 10 ноября 2009

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

for line in (l for l in open(filename) if not l.startswith('#')):
    # do something with line

(l for ... ) называется «выражением генератора», которое действует здесь как итератор обтекания, который отфильтровывает все ненужные строки из файла при его итерации по нему. Не путайте это с тем же самым в квадратных скобках [l for ... ], которые являются «списком», который сначала прочитает все строки из файла в память и только затем начнет перебирать его.

Иногда вам может понадобиться сделать его менее однострочным и более читабельным:

lines = open(filename)
lines = (l for l in lines if ... )
# more filters and mappings you might want
for line in lines:
    # do something with line

Все фильтры будут выполняться на лету за одну итерацию.

1 голос
/ 10 марта 2017

Используйте регулярное выражение re.compile("^(?:\s+)*#|(?:\s+)"), чтобы пропустить новые строки и комментарии.

0 голосов
/ 17 ноября 2009

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

for line  in lines:
    if '#' not in line:
        #do something

Это будет игнорировать всю строку, хотя ответ, который включает в себя rpartition, имеет мое повышение, поскольку он может включать любую информацию до #

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...