Питон:Как игнорировать строки #comment при чтении в файле
Вопрос
В Python я только что прочитал строку из текстового файла, и я хотел бы знать, как закодировать игнорирование комментариев с хэшем # в начале строки.
Я думаю, это должно быть что-то вроде этого:
for
if line !contain #
then ...process line
else end for loop
Но я новичок в Python и не знаю синтаксиса
Решение
вы можете использовать начинается с()
например
for line in open("file"):
li=line.strip()
if not li.startswith("#"):
print line.rstrip()
Другие советы
Я рекомендую вам не игнорировать всю строку целиком, когда вы видите #
характер;просто игнорируйте остальную часть строки.Вы можете легко сделать это с помощью вызываемой функции string method partition
:
with open("filename") as f:
for line in f:
line = line.partition('#')[0]
line = line.rstrip()
# ... do something with line ...
partition
возвращает кортеж:все, что находится перед строкой раздела, строка раздела и все, что находится после строки раздела.Итак, путем индексации с помощью [0]
мы берем только ту часть, которая находится перед строкой раздела.
Редактировать:Если вы используете версию 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
Я действительно не хотел усложнять вопрос с пометкой "новичок", но этот конечный автомат достаточно прост, и я надеюсь, что он будет интересным.
Я опоздал, но проблема с обработкой стиля оболочки (или стиля Python) #
комментарии очень распространены.
Я использую какой-то код почти каждый раз, когда читаю текстовый файл.
Проблема в том, что он неправильно обрабатывает цитируемые или экранированные комментарии..Но это работает для простых случаев и легко.
for line in whatever:
line = line.split('#',1)[0].strip()
if not line:
continue
# process line
Более надежное решение — использовать шлекс:
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
Это самая короткая из возможных форм:
for line in open(filename):
if line.startswith('#'):
continue
# PROCESS LINE HERE
Тот самый startswith()
метод для строки возвращает True, если строка, в которой вы его вызываете, начинается со строки, которую вы передали.
Хотя это нормально в некоторых обстоятельствах, таких как сценарии командной оболочки, у этого есть две проблемы.Во-первых, в нем не указано, как открыть файл.Режимом открытия файла по умолчанию является 'r'
, что означает "прочитать файл в двоичном режиме".Поскольку вы ожидаете получить текстовый файл, лучше открыть его с помощью 'rt'
.Хотя это различие неуместно в UNIX-подобных операционных системах, оно важно в Windows (и на Mac до OS X).
Вторая проблема - это дескриптор открытого файла.Тот самый open()
функция возвращает объект file, и считается хорошей практикой закрывать файлы, когда вы закончите с ними.Чтобы сделать это, вызовите close()
метод для объекта.Теперь Python будет вероятно сделаю это для тебя, в конце концов; в Python объекты подсчитываются по ссылкам, и когда счетчик ссылок объекта становится равным нулю, он освобождается, и в какой-то момент после освобождения объекта Python вызовет его деструктор (вызываемый специальный метод __del__
).Обратите внимание, что я сказал вероятно: У Python есть плохая привычка фактически не вызывать деструктор для объектов, количество ссылок на которые падает до нуля незадолго до завершения программы.Я думаю, это в спешке!
Для недолговечных программ, таких как сценарии командной оболочки, и особенно для файловых объектов, это не имеет значения.Ваша операционная система автоматически очистит все дескрипторы файлов, оставшиеся открытыми, когда программа завершит работу.Но если вы открыли файл, прочитали содержимое, а затем начали длительное вычисление без предварительного явного закрытия дескриптора файла, Python, скорее всего, оставит дескриптор файла открытым во время ваших вычислений.И это плохая практика.
Эта версия будет работать в любой версии Python 2.x и устраняет обе проблемы, которые я обсуждал выше:
f = open(file, 'rt')
for line in f:
if line.startswith('#'):
continue
# PROCESS LINE HERE
f.close()
Это наилучшая общая форма для старых версий Python.
Как предложил steveha, использование инструкции "with" в настоящее время считается наилучшей практикой.Если вы используете версию 2.6 или выше, вы должны написать это следующим образом:
with open(filename, 'rt') as f:
for line in f:
if line.startswith('#'):
continue
# PROCESS LINE HERE
Оператор "with" очистит дескриптор файла для вас.
В своем вопросе вы сказали "строки, начинающиеся с #", так что это то, что я показал вам здесь.Если вы хотите отфильтровать строки, начинающиеся с необязательный пробел и тогда a '#', вы должны убрать пробел, прежде чем искать '#'.В таком случае вам следует изменить это:
if line.startswith('#'):
к этому:
if line.lstrip().startswith('#'):
В Python строки неизменяемы, поэтому это не меняет значения line
.Тот самый lstrip()
метод возвращает копию строки со всеми удаленными начальными пробелами.
Недавно я обнаружил, что функция генератора отлично справляется с этой задачей.Я использовал аналогичные функции для пропуска строк комментариев, пустых строк и т. д.
Я определяю свою функцию как
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
Это можно повторно использовать во всем моем коде, и я могу добавить любую дополнительную обработку/ведение журнала/и т. д.что мне нужно.
Я знаю, что это старый поток, но это функция генератора, которую я использую для своих собственных целей.Он удаляет комментарии независимо от того, где они появляются в строке, а также удаляет начальные / завершающие пробелы и пустые строки.Следующий исходный текст:
# 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 uncommented non-blank line from ``item`` with
comments and leading/trailing whitespace stripped.
"""
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)
Более компактная версия выражения фильтрации также может выглядеть так:
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
Все фильтры будут выполняться «на лету» за одну итерацию.
Используйте регулярное выражение re.compile("^(?:\s+)*#|(?:\s+)")
чтобы пропустить новые строки и комментарии.
Я склонен использовать
for line in lines:
if '#' not in line:
#do something
Это будет игнорировать всю строку, хотя ответ, включающий rpartition, имеет мой плюс, поскольку он может включать любую информацию до #