다음과 같이 정의 된 여러 줄 문자열이 있습니다.
foo = """
this is
a multi-line string.
"""
이 문자열은 내가 쓰고있는 파서의 테스트 입력으로 사용했습니다. 파서 함수는 file
입력 으로 -object를 수신 하고 반복합니다. 또한 next()
줄을 건너 뛰기 위해 메서드를 직접 호출 하므로 반복자가 아닌 입력으로 반복기가 필요합니다. file
-object가 텍스트 파일의 줄을 넘기는 것처럼 해당 문자열의 개별 줄을 반복하는 반복기가 필요 합니다. 물론 다음과 같이 할 수 있습니다.
lineiterator = iter(foo.splitlines())
더 직접적인 방법이 있습니까? 이 시나리오에서 문자열은 분할을 위해 한 번 통과 한 다음 파서가 다시 통과해야합니다. 내 테스트 케이스에서는 문제가되지 않습니다. 문자열이 매우 짧기 때문에 호기심에서 묻고 있습니다. 파이썬에는 그러한 것들을위한 유용하고 효율적인 내장 기능이 너무 많지만,이 필요에 맞는 것을 찾을 수 없었습니다.
답변
세 가지 가능성이 있습니다.
foo = """
this is
a multi-line string.
"""
def f1(foo=foo): return iter(foo.splitlines())
def f2(foo=foo):
retval = ''
for char in foo:
retval += char if not char == '\n' else ''
if char == '\n':
yield retval
retval = ''
if retval:
yield retval
def f3(foo=foo):
prevnl = -1
while True:
nextnl = foo.find('\n', prevnl + 1)
if nextnl < 0: break
yield foo[prevnl + 1:nextnl]
prevnl = nextnl
if __name__ == '__main__':
for f in f1, f2, f3:
print list(f())
이것을 메인 스크립트로 실행하면 세 가지 기능이 동일하다는 것을 확인할 수 있습니다. 와 timeit
(과 * 100
에 대한 것은 foo
더 정확한 측정을위한 실질적인 문자열을 얻을 수) :
$ python -mtimeit -s'import asp' 'list(asp.f3())'
1000 loops, best of 3: 370 usec per loop
$ python -mtimeit -s'import asp' 'list(asp.f2())'
1000 loops, best of 3: 1.36 msec per loop
$ python -mtimeit -s'import asp' 'list(asp.f1())'
10000 loops, best of 3: 61.5 usec per loop
list()
반복자가 빌드 된 것이 아니라 순회되도록 하려면 호출이 필요합니다 .
IOW, 순진한 구현이 훨씬 빠르기 때문에 재미도 없습니다. find
호출 시도보다 6 배 빠르며 하위 수준 접근 방식보다 4 배 빠릅니다.
유지해야 할 교훈 : 측정은 항상 좋은 것입니다 (하지만 정확해야합니다). 같은 문자열 메서드 splitlines
는 매우 빠른 방법으로 구현됩니다. 매우 낮은 수준 (특히 +=
매우 작은 조각의 루프)에서 프로그래밍하여 문자열을 조합하는 것은 상당히 느릴 수 있습니다.
편집 : @Jacob의 제안을 추가하여 다른 것과 동일한 결과를 제공하도록 약간 수정했습니다 (한 줄의 후행 공백이 유지됨).
from cStringIO import StringIO
def f4(foo=foo):
stri = StringIO(foo)
while True:
nl = stri.readline()
if nl != '':
yield nl.strip('\n')
else:
raise StopIteration
측정 결과 :
$ python -mtimeit -s'import asp' 'list(asp.f4())'
1000 loops, best of 3: 406 usec per loop
.find
기반 접근 방식 만큼 좋지는 않지만 작은 버그에 덜 취약 할 수 있기 때문에 명심할 가치가 있습니다 ( f3
위와 같이 +1과 -1이 발생하는 모든 루프 는 자동으로 한 번에 하나씩 의심을 불러 일으키고, 다른 기능으로 출력을 확인할 수 있었기 때문에 내 코드도 옳다고 생각하지만 그러한 조정이 부족하고 가져야하는 많은 루프가 있어야합니다. ‘).
그러나 분할 기반 접근 방식은 여전히 규칙입니다.
제쳐두고 : 아마도 더 나은 스타일 f4
은 다음과 같습니다.
from cStringIO import StringIO
def f4(foo=foo):
stri = StringIO(foo)
while True:
nl = stri.readline()
if nl == '': break
yield nl.strip('\n')
적어도 좀 덜 장황합니다. \n
안타깝게도 후행을 제거해야하는 필요성 은 while
루프를 보다 명확하고 빠르게 교체하는 것을 금지합니다 return iter(stri)
( iter
최신 버전의 Python에서 중복 되는 부분은 2.3 또는 2.4 이후로 믿지만 또한 무해합니다). 시도해 볼만한 가치가 있습니다.
return itertools.imap(lambda s: s.strip('\n'), stri)
또는 그것의 변형-그러나 나는 이론적 인 연습이 strip
기초적이고 가장 간단하고 빠른 것이므로 여기서 멈추고 있습니다 .
답변
“파서에 의해 다시”가 무슨 뜻인지 잘 모르겠습니다. 분할이 완료되면 더 이상 문자열을 순회하지 않고 분할 문자열 목록 만 순회합니다 . 문자열의 크기가 절대적으로 크지 않은 한 이것은 실제로 이것을 수행하는 가장 빠른 방법 일 것입니다. 파이썬이 불변의 문자열을 사용한다는 사실 은 항상 새로운 문자열을 만들어야 한다는 것을 의미 합니다. 그래서 이것은 어쨌든 어느 시점에서 이루어져야합니다.
문자열이 매우 크면 단점은 메모리 사용량에 있습니다. 원래 문자열과 분할 문자열 목록이 동시에 메모리에 있으므로 필요한 메모리가 두 배가됩니다. 반복자 접근 방식을 사용하면 “분할”패널티를 지불하지만 필요에 따라 문자열을 작성하여이를 절약 할 수 있습니다. 그러나 문자열이 그렇게 크면 일반적으로 분할되지 않은 문자열도 메모리에 포함 되지 않도록해야 합니다. 파일에서 문자열을 읽는 것이 더 낫습니다. 이미 줄로 반복 할 수 있습니다.
그러나 메모리에 이미 큰 문자열이있는 경우 한 가지 방법은 StringIO를 사용하는 것입니다.이 방법은 문자열에 파일과 유사한 인터페이스를 제공하는 것입니다.이 인터페이스는 줄 단위로 반복하는 것을 포함합니다 (내부적으로 .find를 사용하여 다음 줄 바꿈 찾기). 그러면 다음을 얻을 수 있습니다.
import StringIO
s = StringIO.StringIO(myString)
for line in s:
do_something_with(line)
답변
Modules/cStringIO.c
올바르게 읽으면 상당히 효율적일 것입니다 (다소 장황하지만).
from cStringIO import StringIO
def iterbuf(buf):
stri = StringIO(buf)
while True:
nl = stri.readline()
if nl != '':
yield nl.strip()
else:
raise StopIteration
답변
정규식 기반 검색은 때때로 생성기 접근 방식보다 빠릅니다.
RRR = re.compile(r'(.*)\n')
def f4(arg):
return (i.group(1) for i in RRR.finditer(arg))
답변
나는 당신이 스스로 굴릴 수 있다고 생각합니다.
def parse(string):
retval = ''
for char in string:
retval += char if not char == '\n' else ''
if char == '\n':
yield retval
retval = ''
if retval:
yield retval
이 구현이 얼마나 효율적인지 잘 모르겠지만 문자열을 한 번만 반복합니다.
음, 발전기.
편집하다:
물론 수행하려는 구문 분석 작업 유형을 추가하고 싶을 수도 있지만 매우 간단합니다.
답변
후행 개행 문자를 포함하여 행을 생성하는 “파일”을 반복 할 수 있습니다. 문자열에서 “가상 파일”을 만들려면 다음을 사용할 수 있습니다 StringIO
.
import io # for Py2.7 that would be import cStringIO as io
for line in io.StringIO(foo):
print(repr(line))