10

私はこのようなテキストファイルを持っています:

11
2
3
4

11

111

Python 2.7を使用して、それを行のリストのリストに変換したいと思います。ここで、改行は内側のリストの項目を分割し、空の行は外側のリストの項目を分割します。そのようです:

[["11","2","3","4"],["11"],["111"]]

そして、この目的のために、開いているファイルオブジェクトを渡すと、一度に1つずつ内部リストを生成するジェネレーター関数を作成しました。

def readParag(fileObj):
    currentParag = []
    for line in fileObj:
        stripped = line.rstrip()
    if len(stripped) > 0: currentParag.append(stripped)
    elif len(currentParag) > 0:
        yield currentParag
        currentParag = []

これは問題なく機能し、リスト内包表記内から呼び出すことができ、目的の結果が得られます。しかし、その後、同じことをより簡潔に使用できる可能性があることに気付きitertools.takewhileました(ジェネレーター関数をジェネレーター式として書き直すためですが、ここではそのままにしておきます)。これは私が試したものです:

from itertools import takewhile    
def readParag(fileObj):
    yield [ln.rstrip() for ln in takewhile(lambda line: line != "\n", fileObj)]

この場合、結果のジェネレーターは1つの結果(期待される最初の結果、つまり["11","2","3","4"])のみを生成します。そのnextメソッドを再度呼び出すtakewhile(lambda line: line != "\n", fileObj)と、ファイルの残りの部分で再度評価され、別のリストが生成されることを期待していました。しかし、いいえ:StopIteration代わりに入手しました。したがって、式はジェネレーターオブジェクトが作成されたときに一度だけ評価され、結果のジェネレーターオブジェクトのメソッドtake whileを呼び出すたびには評価されていないと推測しました。next

この仮定から、ジェネレーター関数をもう一度呼び出したらどうなるのだろうと思いました。その結果、新しいジェネレーターオブジェクトが作成され、1つの結果(予想される2番目の結果)が生成されてから、私["11"]StopIteration返されました。したがって、実際には、これをジェネレーター関数として記述すると、通常の関数として記述しreturn、リストを作成する代わりに編集した場合と同じ結果が得られyieldます。

ジェネレータの代わりに使用する独自のクラスを作成することで、この問題を解決できると思います(この質問に対するJohn Millikinの回答のように)。しかし、要点は、元のジェネレーター関数(おそらくジェネレーター式でさえ)よりも簡潔なものを書きたいと思っていたということです。誰かが私が間違っていることとそれを正しくする方法を教えてもらえますか?

4

6 に答える 6

26

あなたがやろうとしていることは、完璧な仕事ですgroupby

from itertools import groupby

def read_parag(filename):
    with open(filename) as f:
        for k,g in groupby((line.strip() for line in f), bool):
            if k:
                yield list(g)

これは与えるでしょう:

>>> list(read_parag('myfile.txt')
[['11', '2', '3', '4'], ['11'], ['111']]

または一行で:

[list(g) for k,g in groupby((line.strip() for line in open('myfile.txt')), bool) if k]
于 2012-08-07T19:24:56.743 に答える
7

他の答えはここで何が起こっているかを説明するのに良い仕事をします、あなたはtakewhileあなたの現在の発電機がしない複数回呼び出す必要があります。組み込みiter()関数とセンチネル引数を使用して、必要な動作を取得するためのかなり簡潔な方法を次に示します。

from itertools import takewhile

def readParag(fileObj):
    cond = lambda line: line != "\n"
    return iter(lambda: [ln.rstrip() for ln in takewhile(cond, fileObj)], [])
于 2012-08-07T19:35:57.387 に答える
6

これはまさにその.takewhile()振る舞い方です。条件がtrueである間は、基になるiterableから要素を返し、falseになるとすぐに、反復実行ステージに永続的に切り替わります。

これがイテレータの動作方法であることに注意してください。StopIterationを上げるということは、私を反復するのをやめて、完了したということです。

「イテレータ」に関するPython用語集から:

データのストリームを表すオブジェクト。イテレータのnext()メソッドを繰り返し呼び出すと、ストリーム内の連続するアイテムが返されます。使用可能なデータがなくなると、StopIteration代わりに例外が発生します。この時点で、イテレータオブジェクトは使い果たされ、そのnext()メソッドへのそれ以上の呼び出しはStopIteration再び発生します。

と組み合わせtakewhiletee、次のバッチでさらに結果があるかどうかを確認できます。

import itertools

def readParag(filename):
    with open(filename) as f:
        while True:
            paras = itertools.takewhile(lambda l: l.strip(), f)
            test, paras = itertools.tee(paras)
            test.next()  # raises StopIteration when the file is done
            yield (l.strip() for l in paras)

これによりジェネレーターが生成されるため、生成される各アイテム自体がジェネレーターになります。これが機能し続けるには、これらのジェネレーターのすべての要素を消費する必要があります。別の回答にリストされているgroupbyメソッドについても同じことが言えます。

于 2012-08-07T19:21:22.153 に答える
2

ファイルの内容がメモリに収まる場合は、グループを空白行で区切るはるかに簡単な方法があります。

with open("filename") as f:
    groups = [group.split() for group in f.read().split("\n\n")]

re.split()このアプローチは、代わりにを使用str.split()し、4つ以上の連続した改行から生じる潜在的な空のグループを除外することによって、より堅牢にすることができます。

于 2012-08-09T15:58:48.787 に答える
1

これは、の文書化された動作ですtakewhile。条件が真である、それはかかります。後で条件が再び真になっても、再起動しません。

簡単な修正は、関​​数がループ内でtakewhileを呼び出すだけにし、takewhileが返すものがなくなったときに(つまり、ファイルの最後で)停止することです。

def readParag(fileObj):
    while True:      
        nextList = [ln.rstrip() for ln in takewhile(lambda line: line != "\n", fileObj)]
        if not nextList:
            break
        yield nextList
于 2012-08-07T19:28:44.963 に答える
0

あなたは複数回takewhileを呼び出すことができます:

>>> def readParagGenerator(fileObj):
...     group = [ln.rstrip() for ln in takewhile(lambda line: line != "\n", fileObj)]
...     while len(group) > 0:
...         yield group
...         group = [ln.rstrip() for ln in takewhile(lambda line: line != "\n", fileObj)]
... 
>>> list(readParagGenerator(StringIO(F)))
[['11', '2', '3', '4'], ['11'], ['111']]
于 2012-08-07T19:31:07.780 に答える