[python] 파이썬에`string.split ()`의 생성기 버전이 있습니까?

string.split()목록 인스턴스를 반환 합니다. 대신 생성기 를 반환하는 버전이 있습니까? 생성기 버전을 사용하지 않는 이유가 있습니까?



답변

re.finditer상당히 최소한의 메모리 오버 헤드 를 사용 하는 것은 매우 가능성이 높습니다 .

def split_iter(string):
    return (x.group(0) for x in re.finditer(r"[A-Za-z']+", string))

데모:

>>> list( split_iter("A programmer's RegEx test.") )
['A', "programmer's", 'RegEx', 'test']

편집 : 내 테스트 방법이 정확하다고 가정하고 파이썬 3.2.1에서 일정한 메모리를 차지한다는 것을 방금 확인했습니다. 나는 매우 큰 크기 (1GB 정도)의 문자열을 만든 다음 for루프 (추가 메모리를 생성 할 수있는 목록 이해가 아님) 를 사용하여 반복 가능 항목을 반복했습니다 . 이로 인해 메모리가 눈에 띄게 증가하지는 않았습니다 (즉, 메모리가 증가하면 1GB 문자열보다 훨씬 적음).


답변

내가 생각할 수있는 가장 효율적인 방법 offsetstr.find()메소드 의 매개 변수를 사용하여 작성 하는 것입니다. 이것은 많은 메모리 사용을 피하고 필요하지 않을 때 regexp의 오버 헤드에 의존합니다.

[2016-8-2 편집 : 정규식 구분 기호를 선택적으로 지원하도록 업데이트 됨]

def isplit(source, sep=None, regex=False):
    """
    generator version of str.split()

    :param source:
        source string (unicode or bytes)

    :param sep:
        separator to split on.

    :param regex:
        if True, will treat sep as regular expression.

    :returns:
        generator yielding elements of string.
    """
    if sep is None:
        # mimic default python behavior
        source = source.strip()
        sep = "\\s+"
        if isinstance(source, bytes):
            sep = sep.encode("ascii")
        regex = True
    if regex:
        # version using re.finditer()
        if not hasattr(sep, "finditer"):
            sep = re.compile(sep)
        start = 0
        for m in sep.finditer(source):
            idx = m.start()
            assert idx >= start
            yield source[start:idx]
            start = m.end()
        yield source[start:]
    else:
        # version using str.find(), less overhead than re.finditer()
        sepsize = len(sep)
        start = 0
        while True:
            idx = source.find(sep, start)
            if idx == -1:
                yield source[start:]
                return
            yield source[start:idx]
            start = idx + sepsize

원하는대로 사용할 수 있습니다 …

>>> print list(isplit("abcb","b"))
['a','c','']

find () 또는 슬라이싱이 수행 될 때마다 문자열 내에서 검색하는 데 약간의 비용이 발생하지만 문자열은 메모리에서 연속적인 배열로 표현되기 때문에 최소화되어야합니다.


답변

이것은 너무 많은 하위 문자열을 할당하는 문제가없는 split()via 구현의 생성기 버전입니다 re.search().

import re

def itersplit(s, sep=None):
    exp = re.compile(r'\s+' if sep is None else re.escape(sep))
    pos = 0
    while True:
        m = exp.search(s, pos)
        if not m:
            if pos < len(s) or sep is not None:
                yield s[pos:]
            break
        if pos < m.start() or sep is not None:
            yield s[pos:m.start()]
        pos = m.end()


sample1 = "Good evening, world!"
sample2 = " Good evening, world! "
sample3 = "brackets][all][][over][here"
sample4 = "][brackets][all][][over][here]["

assert list(itersplit(sample1)) == sample1.split()
assert list(itersplit(sample2)) == sample2.split()
assert list(itersplit(sample3, '][')) == sample3.split('][')
assert list(itersplit(sample4, '][')) == sample4.split('][')

편집 : 구분 문자가 제공되지 않은 경우 주변 공백 처리를 수정했습니다.


답변

제안 된 다양한 방법에 대한 성능 테스트를 수행했습니다 (여기서는 반복하지 않겠습니다). 몇 가지 결과 :

  • str.split (기본값 = 0.3461570239996945
  • 수동 검색 (문자 별) (Dave Webb의 답변 중 하나) = 0.8260340550004912
  • re.finditer (ninjagecko의 답변) = 0.698872097000276
  • str.find (Eli Collins의 답변 중 하나) = 0.7230395330007013
  • itertools.takewhile (Ignacio Vazquez-Abrams의 답변) = 2.023023967998597
  • str.split(..., maxsplit=1) 재귀 = N / A †

† (재귀 답변 string.split과 함께 maxsplit = 1, 적절한 시간에 완료하는 데 실패) 지정된 string.splitS가 짧은 문자열에 더 잘 작동 할 수 있습니다 속도를하지만, 메모리 어쨌든 문제가되지 않는 경우 그때 나는 짧은 문자열에 대한 사용 사례를 볼 수 없습니다.

다음을 사용하여 테스트 timeit했습니다.

the_text = "100 " * 9999 + "100"

def test_function( method ):
    def fn( ):
        total = 0

        for x in method( the_text ):
            total += int( x )

        return total

    return fn

이것은 string.split메모리 사용량에도 불구하고 왜 훨씬 더 빠른지에 대한 또 다른 질문을 제기 합니다.


답변

여기에 다른 답변보다 훨씬 빠르고 완벽하게 구현 된 구현이 있습니다. 다른 경우에 대해 4 개의 별도 하위 기능이 있습니다.

str_split함수 의 독 스트링을 복사하겠습니다 .


str_split(s, *delims, empty=None)

s나머지 인수로 문자열 을 분할하여 빈 부분을 생략 할 수 있습니다 ( empty키워드 인수가이를 담당합니다). 이것은 생성기 함수입니다.

구분 기호가 하나만 제공되면 문자열이 단순히 분할됩니다.
empty다음입니다 True기본적으로.

str_split('[]aaa[][]bb[c', '[]')
    -> '', 'aaa', '', 'bb[c'
str_split('[]aaa[][]bb[c', '[]', empty=False)
    -> 'aaa', 'bb[c'

여러 구분 기호가 제공되면 기본적으로 해당 구분 기호의 가능한 가장 긴 시퀀스로 문자열이 분할됩니다. 또는 empty로 설정된
경우 True구분 기호 사이의 빈 문자열도 포함됩니다. 이 경우 구분 기호는 단일 문자 일 수 있습니다.

str_split('aaa, bb : c;', ' ', ',', ':', ';')
    -> 'aaa', 'bb', 'c'
str_split('aaa, bb : c;', *' ,:;', empty=True)
    -> 'aaa', '', 'bb', '', '', 'c', ''

구분 기호가 제공되지 않으면 string.whitespace이 사용 str.split()되므로이 함수가 생성기라는 점을 제외 하면 효과는와 동일 합니다.

str_split('aaa\\t  bb c \\n')
    -> 'aaa', 'bb', 'c'

import string

def _str_split_chars(s, delims):
    "Split the string `s` by characters contained in `delims`, including the \
    empty parts between two consecutive delimiters"
    start = 0
    for i, c in enumerate(s):
        if c in delims:
            yield s[start:i]
            start = i+1
    yield s[start:]

def _str_split_chars_ne(s, delims):
    "Split the string `s` by longest possible sequences of characters \
    contained in `delims`"
    start = 0
    in_s = False
    for i, c in enumerate(s):
        if c in delims:
            if in_s:
                yield s[start:i]
                in_s = False
        else:
            if not in_s:
                in_s = True
                start = i
    if in_s:
        yield s[start:]


def _str_split_word(s, delim):
    "Split the string `s` by the string `delim`"
    dlen = len(delim)
    start = 0
    try:
        while True:
            i = s.index(delim, start)
            yield s[start:i]
            start = i+dlen
    except ValueError:
        pass
    yield s[start:]

def _str_split_word_ne(s, delim):
    "Split the string `s` by the string `delim`, not including empty parts \
    between two consecutive delimiters"
    dlen = len(delim)
    start = 0
    try:
        while True:
            i = s.index(delim, start)
            if start!=i:
                yield s[start:i]
            start = i+dlen
    except ValueError:
        pass
    if start<len(s):
        yield s[start:]


def str_split(s, *delims, empty=None):
    """\
Split the string `s` by the rest of the arguments, possibly omitting
empty parts (`empty` keyword argument is responsible for that).
This is a generator function.

When only one delimiter is supplied, the string is simply split by it.
`empty` is then `True` by default.
    str_split('[]aaa[][]bb[c', '[]')
        -> '', 'aaa', '', 'bb[c'
    str_split('[]aaa[][]bb[c', '[]', empty=False)
        -> 'aaa', 'bb[c'

When multiple delimiters are supplied, the string is split by longest
possible sequences of those delimiters by default, or, if `empty` is set to
`True`, empty strings between the delimiters are also included. Note that
the delimiters in this case may only be single characters.
    str_split('aaa, bb : c;', ' ', ',', ':', ';')
        -> 'aaa', 'bb', 'c'
    str_split('aaa, bb : c;', *' ,:;', empty=True)
        -> 'aaa', '', 'bb', '', '', 'c', ''

When no delimiters are supplied, `string.whitespace` is used, so the effect
is the same as `str.split()`, except this function is a generator.
    str_split('aaa\\t  bb c \\n')
        -> 'aaa', 'bb', 'c'
"""
    if len(delims)==1:
        f = _str_split_word if empty is None or empty else _str_split_word_ne
        return f(s, delims[0])
    if len(delims)==0:
        delims = string.whitespace
    delims = set(delims) if len(delims)>=4 else ''.join(delims)
    if any(len(d)>1 for d in delims):
        raise ValueError("Only 1-character multiple delimiters are supported")
    f = _str_split_chars if empty else _str_split_chars_ne
    return f(s, delims)

이 함수는 Python 3에서 작동하며 매우 추악하지만 쉬운 수정을 적용하여 2 버전과 3 버전 모두에서 작동하도록 할 수 있습니다. 함수의 첫 줄은 다음과 같이 변경해야합니다.

def str_split(s, *delims, **kwargs):
    """...docstring..."""
    empty = kwargs.get('empty')


답변

아니요,하지만 itertools.takewhile().

편집하다:

매우 간단하고 반쯤 손상된 구현 :

import itertools
import string

def isplitwords(s):
  i = iter(s)
  while True:
    r = []
    for c in itertools.takewhile(lambda x: not x in string.whitespace, i):
      r.append(c)
    else:
      if r:
        yield ''.join(r)
        continue
      else:
        raise StopIteration()


답변

생성기 버전의 split(). 생성기 객체는 반복 할 전체 문자열을 포함해야하므로 생성기를 사용하여 메모리를 절약하지 않을 것입니다.

하나를 작성하고 싶다면 꽤 쉬울 것입니다.

import string

def gsplit(s,sep=string.whitespace):
    word = []

    for c in s:
        if c in sep:
            if word:
                yield "".join(word)
                word = []
        else:
            word.append(c)

    if word:
        yield "".join(word)