web-dev-qa-db-ja.com

Python:ファイルを読み込むときに#comment行を無視する方法

Pythonでは、テキストファイルから行を読み取ったばかりで、行の先頭にハッシュ#が付いたコメントを無視するようにコーディングする方法を知りたいと思います。

私はそれがこのようなものになるはずだと思う:

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

しかし、私はPythonが初めてで、構文がわからない

41
John

startswith() を使用できます

例えば

for line in open("file"):
    li=line.strip()
    if not li.startswith("#"):
        print line.rstrip()
55
ghostdog74

_#_文字が表示されている場合、行全体を無視しないことをお勧めします。残りの行は無視してください。 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()メソッドを1分割後に停止します。 (_[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
_

「初心者」とタグ付けされた質問でこれを複雑にしたくはありませんでしたが、このステートマシンはかなり単純であり、興味深いものになることを願っています。

42
steveha

私はこれに遅れていますが、シェルスタイルの処理の問題(または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 
8
travc

これは最短の形式です。

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

文字列のstartswith()メソッドは、呼び出した文字列が渡した文字列で始まる場合にTrueを返します。

これは、シェルスクリプトのような状況では問題ありませんが、2つの問題があります。まず、ファイルを開く方法を指定しません。ファイルを開くためのデフォルトのモードは_'r'_です。これは、「バイナリモードでファイルを読み取る」ことを意味します。テキストファイルを期待しているので、_'rt'_で開くことをお勧めします。この区別は、UNIXライクなオペレーティングシステムでは無関係ですが、Windows(およびOS X以前のMac)では重要です。

2番目の問題は、開いているファイルハンドルです。 open()関数はファイルオブジェクトを返します。ファイルを使い終わったら、ファイルを閉じることをお勧めします。そのためには、オブジェクトでclose()メソッドを呼び出します。さて、Pythonはおそらくこれをあなたのために行います、最終的に; in 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」ステートメントは、ファイルハンドルをクリーンアップします。

あなたの質問では、「#で始まる行」と言ったので、ここで紹介しました。 optional whitespaceおよびthen a '#'で始まる行を除外する場合、 '#'を探す前に空白を削除する必要があります。その場合、これを変更する必要があります。

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

これに:

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

Pythonでは、文字列は不変であるため、lineの値は変更されません。 lstrip()メソッドは、先頭の空白をすべて削除した文字列のコピーを返します。

6
Larry Hastings

私は最近、ジェネレーター関数がこれの素晴らしい仕事をすることを発見しました。同様の機能を使用して、コメント行、空白行などをスキップしました。

関数を次のように定義します

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

これはすべてのコードで再利用可能であり、追加の処理/ロギング/などを追加できます。必要なこと。

5
Tim Whitcomb

私はこれが古いスレッドであることを知っていますが、これは私が自分の目的のために使用するジェネレーター関数です。行のどこに表示されていてもコメントを削除し、先頭/末尾の空白と空白行を削除します。次のソーステキスト:

# 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 __== '__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 __== '__main__':
    with open('hosts.txt', 'r') as f:
        hosts = strip_comments(f)

    for Host in hosts:
        print('\'%s\'' % Host)
4
Doug R.

フィルタリング式のよりコンパクトなバージョンも次のようになります。

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

(l for ... )は「ジェネレーター式」と呼ばれ、ここでは、反復処理中にファイルから不要な行をすべて除外するラッピングイテレーターとして機能します。四角いブレーキの同じものと混同しないでください[l for ... ]これは「リスト内包表記」で、最初にファイルからすべての行をメモリに読み込んでから、その行に対して繰り返し処理を開始します。

場合によっては、1行ではなく読みやすくしたい場合があります。

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回の反復でオンザフライで実行されます。

3
isagalaev

Regex re.compile("^(?:\s+)*#|(?:\s+)")を使用して、新しい行とコメントをスキップします。

1
Revanth

使用する傾向がある

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

これは行全体を無視しますが、rpartitionを含む答えには、#より前の情報を含めることができるため、私の賛成票があります

0
Simon Walker