파이썬에서 가장 효율적인 문자열 연결 방법은 무엇입니까?


148

(같은 파이썬에서 어떤 효율적인 대량 문자열 연결 방법이 모두 StringBuilder C # 또는 StringBuffer를 자바는)? 다음과 같은 방법을 찾았 습니다 .

  • 사용하여 간단한 연결 +
  • 문자열리스트와 join메소드 사용
  • 모듈 UserString에서 사용MutableString
  • 문자 배열과 array모듈 사용
  • 모듈 cStringIO에서 사용StringIO

그러나 전문가가 무엇을 사용하거나 제안합니까? 그 이유는 무엇입니까?

[ 관련 질문은 여기 ]



알려진 조각을 하나로 연결하기 위해 Python 3.6 f''에는 이전 Python 버전의 대안보다 빠른 형식 문자열이 있습니다.
Antti Haapala

답변:


127

당신은 이것에 관심이있을 것입니다 : Guido 의 최적화 일화 . 이 기사는 오래된 기사이며 다음과 같은 것들의 존재를 앞두고 있다는 것을 기억할 가치가 있지만 (거의 같지는 ''.join않지만 string.joinfields)

그것의 장점으로, 문제를 해결할 수 있다면 array모듈 가장 빠를 있습니다. 그러나 ''.join아마도 충분히 빠르며 관용적이기 때문에 다른 파이썬 프로그래머가 이해하기 쉽습니다.

마지막으로, 최적화의 황금률 : 당신이 알아야 할 것이 아니라면 추측하지 말고 측정하지 않으면 최적화하지 마십시오.

timeit모듈을 사용하여 다른 방법을 측정 할 수 있습니다 . 그것은 인터넷에서 추측을하는 임의의 낯선 사람 대신에 가장 빠른 것을 알려줄 수 있습니다 .


1
최적화시기에 대한 요점을 추가하고 싶을 때 : 최악의 경우를 테스트해야합니다. 예를 들어, 현재 코드가 0.17 초에서 170 초로 실행되도록 샘플을 늘릴 수 있습니다. 변동이 적기 때문에 더 큰 샘플 크기로 테스트하고 싶습니다.
Flipper

2
"필요할 때까지 최적화하지 마십시오." 명목상 다른 관용구를 사용하지 않고 약간의 노력만으로 코드의 재 작업을 피할 수 없다면.
jeremyjjbrown

1
당신이 알아야 할 곳 중 하나는 인터뷰입니다. 불행히도 나는 이것에 관한 현대 기사를 찾지 못했습니다. (1) Java / C # 문자열이 2017 년에도 여전히 그렇게 나쁩니 까? (2) C ++는 어떻습니까? (3) 이제 수백만 개의 연결을 수행 해야하는 경우에 중점을 둔 Python의 최신 및 최고에 대해 이야기하십시오. 우리는 조인이 선형 시간에 작동한다고 믿을 수 있습니까?
user1854182

"충분히 빠름"은 무엇을 의미 .join()합니까? 주요 질문은, a) 연결을위한 문자열의 사본을 생성하는 것입니다 ()와 비슷 s = s + 'abc'합니다 (O (n) 런타임이 필요합니다) 또는 b) 사본을 만들지 않고 기존 문자열에 단순히 추가합니다 .O (1) ?
CGFoX

64

''.join(sequenceofstrings) 가장 단순하고 빠른 방식으로 가장 잘 작동합니다.


3
@mshsayem, 파이썬에서 시퀀스는 열거 가능한 객체, 심지어 함수일 수 있습니다.
Nick Dandoulakis

2
나는 ''.join(sequence)관용구를 절대적으로 좋아합니다 . 쉼표로 구분 된 목록을 생성하는 데 특히 유용합니다 : ', '.join([1, 2, 3])gives the string '1, 2, 3'.
앤드류 키튼

7
@mshsayem : "".join(chr(x) for x in xrange(65,91))---이 경우 결합 할 인수는 생성자 표현식을 통해 작성된 반복자입니다. 생성되는 임시 목록이 없습니다.
balpha

2
@balpha : 그러나 생성기 버전은 목록 이해 버전보다 느립니다 : C : \ temp> python -mtimeit " ''.join (chr (x) x in xrange (65,91))"100000 루프, 최고 3 : 9.1 루프 당 usec C : \ temp> python -mtimeit " ''.join ([xrange (65,91)]에서 x의 경우 chr (x)) 100000 루프, 루프 당 3 : 7.1 usec 중 최고
hughdbrown

1
@ hughdbrown, 그렇습니다. wazoo (일반적인 timeit case) listcomp가 여유 메모리를 확보하면 genexp보다 20-30 % 더 잘 최적화 될 수 있습니다. 기억력이 빡빡한 것들이 다를 때 (제때에 재현하기 어렵다!-)
Alex Martelli

58

Python 3.6은 Literal String Interpolation 으로 알려진 구성 요소의 문자열 연결을위한 게임을 변경했습니다 .

mkoistinen의 답변 에서 테스트 사례가 주어지면 문자열이 있습니다.

domain = 'some_really_long_example.com'
lang = 'en'
path = 'some/really/long/path/'

경쟁자는

  • f'http://{domain}/{lang}/{path}'- 0.151 μS

  • 'http://%s/%s/%s' % (domain, lang, path) -0.321µs

  • 'http://' + domain + '/' + lang + '/' + path -0.356µs

  • ''.join(('http://', domain, '/', lang, '/', path))- 0.249 μS (일정한 길이의 튜플을 구축하는 것은 약간 빠른 일정한 길이의 목록을 구축하는 것보다 것을 통지).

따라서 현재 가장 짧고 가장 아름다운 코드도 가장 빠릅니다.

Python 3.6의 알파 버전에서는 f''문자열 구현이 가장 느 렸습니다. 실제로 생성 된 바이트 코드는 인수없이 불변으로 반환되는 ''.join()불필요한 호출이 str.__format__있는 경우와 거의 같습니다 self. 이러한 비 효율성은 3.6 최종 전에 해결되었습니다.

속도는 +내 컴퓨터에서 연결된 Python 2의 가장 빠른 방법과 대조 될 수 있습니다 . 그리고 그 소요 0.203 8 비트 문자열 μS 및 0.259 문자열이 모든 경우 유니 μS한다.


38

그것은 당신이하는 일에 달려 있습니다.

Python 2.5 이후에는 + 연산자를 사용한 문자열 연결이 매우 빠릅니다. 두 개의 값을 연결하는 경우 + 연산자를 사용하는 것이 가장 좋습니다.

>>> x = timeit.Timer(stmt="'a' + 'b'")
>>> x.timeit()
0.039999961853027344

>>> x = timeit.Timer(stmt="''.join(['a', 'b'])")
>>> x.timeit()
0.76200008392333984

그러나 루프에서 문자열을 구성하는 경우 목록 결합 방법을 사용하는 것이 좋습니다.

>>> join_stmt = """
... joined_str = ''
... for i in xrange(100000):
...   joined_str += str(i)
... """
>>> x = timeit.Timer(join_stmt)
>>> x.timeit(100)
13.278000116348267

>>> list_stmt = """
... str_list = []
... for i in xrange(100000):
...   str_list.append(str(i))
... ''.join(str_list)
... """
>>> x = timeit.Timer(list_stmt)
>>> x.timeit(100)
12.401000022888184

...하지만 차이가 눈에 띄기 전에 상대적으로 많은 수의 문자열을 모아야합니다.


2
1) 첫 번째 측정에서 아마도 시간이 걸리는 목록 구성 일 것입니다. 튜플을 사용해보십시오. 2) CPython을 수행하는, 그러나 다른 파이썬 구현 +와 방법이 더 균일하게 잘 수행하고 + =
u0b34a0f6ae

22

John Fouhy의 답변에 따르면, 필요한 경우가 아니라면 최적화하지 마십시오. 그러나 여기에 있고이 질문을하는 경우 반드시 해야하기 때문일 수 있습니다 . 필자의 경우 문자열 변수에서 일부 URL을 빨리 조립해야했습니다. 나는 (지금까지) 문자열 형식 방법을 고려하고있는 사람이 없다는 것을 알았습니다. 그래서 나는 그것을 시도 할 것이라고 생각했으며, 주로 약간의 관심을 끌기 위해 좋은 보간을 위해 거기에 문자열 보간 연산자를 던질 것이라고 생각했습니다. 솔직히 말해서, 나는 이들 중 어느 것이 직접적인 '+'연산이나 ''.join ()까지 쌓을 것이라고 생각하지 않았습니다. 하지만 그거 알아? Python 2.7.5 시스템 에서 문자열 보간 연산자는 모두 규칙을 지정 하고 string.format ()이 가장 나쁜 성능을 나타냅니다.

# concatenate_test.py

from __future__ import print_function
import timeit

domain = 'some_really_long_example.com'
lang = 'en'
path = 'some/really/long/path/'
iterations = 1000000

def meth_plus():
    '''Using + operator'''
    return 'http://' + domain + '/' + lang + '/' + path

def meth_join():
    '''Using ''.join()'''
    return ''.join(['http://', domain, '/', lang, '/', path])

def meth_form():
    '''Using string.format'''
    return 'http://{0}/{1}/{2}'.format(domain, lang, path)

def meth_intp():
    '''Using string interpolation'''
    return 'http://%s/%s/%s' % (domain, lang, path)

plus = timeit.Timer(stmt="meth_plus()", setup="from __main__ import meth_plus")
join = timeit.Timer(stmt="meth_join()", setup="from __main__ import meth_join")
form = timeit.Timer(stmt="meth_form()", setup="from __main__ import meth_form")
intp = timeit.Timer(stmt="meth_intp()", setup="from __main__ import meth_intp")

plus.val = plus.timeit(iterations)
join.val = join.timeit(iterations)
form.val = form.timeit(iterations)
intp.val = intp.timeit(iterations)

min_val = min([plus.val, join.val, form.val, intp.val])

print('plus %0.12f (%0.2f%% as fast)' % (plus.val, (100 * min_val / plus.val), ))
print('join %0.12f (%0.2f%% as fast)' % (join.val, (100 * min_val / join.val), ))
print('form %0.12f (%0.2f%% as fast)' % (form.val, (100 * min_val / form.val), ))
print('intp %0.12f (%0.2f%% as fast)' % (intp.val, (100 * min_val / intp.val), ))

결과 :

# python2.7 concatenate_test.py
plus 0.360787868500 (90.81% as fast)
join 0.452811956406 (72.36% as fast)
form 0.502608060837 (65.19% as fast)
intp 0.327636957169 (100.00% as fast)

더 짧은 도메인과 더 짧은 경로를 사용하면 여전히 보간이 성공합니다. 그러나 그 차이는 줄이 길수록 더 두드러집니다.

좋은 테스트 스크립트를 얻었으므로 Python 2.6, 3.3 및 3.4에서도 테스트했습니다. 결과는 다음과 같습니다. 파이썬 2.6에서는 더하기 연산자가 가장 빠릅니다! Python 3에서는 join이 승리합니다. 참고 :이 테스트는 시스템에서 매우 반복 가능합니다. 따라서 'plus'는 2.6에서 항상 빠르며 'intp'는 2.7에서 항상 빠르며 'join'은 Python 3.x에서 항상 빠릅니다.

# python2.6 concatenate_test.py
plus 0.338213920593 (100.00% as fast)
join 0.427221059799 (79.17% as fast)
form 0.515371084213 (65.63% as fast)
intp 0.378169059753 (89.43% as fast)

# python3.3 concatenate_test.py
plus 0.409130576998 (89.20% as fast)
join 0.364938726001 (100.00% as fast)
form 0.621366866995 (58.73% as fast)
intp 0.419064424001 (87.08% as fast)

# python3.4 concatenate_test.py
plus 0.481188605998 (85.14% as fast)
join 0.409673971997 (100.00% as fast)
form 0.652010936996 (62.83% as fast)
intp 0.460400978001 (88.98% as fast)

# python3.5 concatenate_test.py
plus 0.417167026084 (93.47% as fast)
join 0.389929617057 (100.00% as fast)
form 0.595661019906 (65.46% as fast)
intp 0.404455224983 (96.41% as fast)

교훈 :

  • 때로는 내 가정이 잘못되었습니다.
  • 시스템 환경에 대해 테스트하십시오. 프로덕션 환경에서 실행됩니다.
  • 문자열 보간은 아직 죽지 않았습니다!

tl; dr :

  • 2.6을 사용하는 경우 + 연산자를 사용하십시오.
  • 2.7을 사용하는 경우 '%'연산자를 사용하십시오.
  • 3.x를 사용하는 경우 ''.join ()을 사용하십시오.

2
참고 : 리터럴 문자열 보간은 3.6+보다 여전히 빠릅니다.f'http://{domain}/{lang}/{path}'
TemporalWolf

1
또한 .format(): 고속에서 저속하기 위해서는 세 가지 형태가 "{}".format(x), "{0}".format(x),"{x}".format(x=x)
TemporalWolf

실제 교훈 : 문제 영역이 작은 경우 (예 : 짧은 문자열 작성) 방법은 중요하지 않습니다. 중요한 경우에도, 예를 들어 실제로 백만 줄을 만드는 경우 오버 헤드가 더 중요합니다. 잘못된 문제에 대해 걱정하는 일반적인 증상입니다. 전체 책을 문자열로 만들 때와 같이 오버 헤드가 중요하지 않은 경우에만 방법 차이가 중요해집니다.
Hui Zhou

7

매번 새로운 연결 후 새 문자열의 상대적 크기에 따라 다릅니다. +연산자를 사용하면 모든 연결에 대해 새 문자열이 작성됩니다. 중개 문자열이 상대적으로 길면 +새 중개 문자열이 저장되기 때문에 속도가 점점 느려집니다.

이 경우를 고려하십시오.

from time import time
stri=''
a='aagsdfghfhdyjddtyjdhmfghmfgsdgsdfgsdfsdfsdfsdfsdfsdfddsksarigqeirnvgsdfsdgfsdfgfg'
l=[]
#case 1
t=time()
for i in range(1000):
    stri=stri+a+repr(i)
print time()-t

#case 2
t=time()
for i in xrange(1000):
    l.append(a+repr(i))
z=''.join(l)
print time()-t

#case 3
t=time()
for i in range(1000):
    stri=stri+repr(i)
print time()-t

#case 4
t=time()
for i in xrange(1000):
    l.append(repr(i))
z=''.join(l)
print time()-t

결과

1 0.00493192672729

2 0.000509023666382

3 0.00042200088501

4 0.000482797622681

1 & 2의 경우 큰 문자열을 추가하고 join ()은 약 10 배 더 빠릅니다. 3 & 4의 경우 작은 문자열을 추가하고 '+'가 약간 더 빠릅니다.


3

알 수없는 크기의 추가 가능한 문자열이 필요한 상황이 발생했습니다. 벤치 마크 결과는 다음과 같습니다 (python 2.7.3).

$ python -m timeit -s 's=""' 's+="a"'
10000000 loops, best of 3: 0.176 usec per loop
$ python -m timeit -s 's=[]' 's.append("a")'
10000000 loops, best of 3: 0.196 usec per loop
$ python -m timeit -s 's=""' 's="".join((s,"a"))'
100000 loops, best of 3: 16.9 usec per loop
$ python -m timeit -s 's=""' 's="%s%s"%(s,"a")'
100000 loops, best of 3: 19.4 usec per loop

이것은 '+ ='가 가장 빠름을 보여줍니다. skymind 링크의 결과가 약간 오래된 것입니다.

(두 번째 예제가 완료되지 않았으므로 최종 목록을 결합해야 함을 알 수 있습니다. 그러나 단순히 목록을 준비하는 것이 문자열 연결보다 시간이 오래 걸린다는 것을 보여줍니다.)


세 번째 및 네 번째 테스트에서 1 초 이하의 시간을 받고 있습니다. 왜 그렇게 높은 시간을 받고 있습니까? pastebin.com/qabNMCHS
bad_keypoints

@ ronnieaka : 그는 모든 테스트에서 1 초 이하의 시간을 받고 있습니다. 그는 3 & 4에 대해 > 1 µs 를 받고 있습니다. 또한 이러한 테스트 (Python 2.7.5, Linux)에서 시간이 느려집니다. CPU, 버전, 빌드 플래그 일 수 있습니다.
Thanatos

이러한 벤치 마크 결과는 쓸모가 없습니다. 특히 첫 번째 경우는 문자열 연결을 수행하지 않고 두 번째 문자열 값을 그대로 반환합니다.
Antti Haapala

3

1 년 후, 파이썬 3.4.3으로 mkoistinen의 답변을 테스트 해 봅시다.

  • 플러스 0.963564149000 (95.83 % 빠름)
  • 0.923408469000 가입 (100.00 % 빠름)
  • 1.501130934000 형식 (61.51 % 빠름)
  • intp 1.019677452000 (90.56 % 빠름)

아무것도 바뀌지 않았다. 조인은 여전히 ​​가장 빠른 방법입니다. 가독성 측면에서 intp가 최선의 선택 임에도 불구하고 intp를 사용하고 싶을 수도 있습니다.


1
어쩌면 mkoistinen 답변에 추가 된 것일 수도 있습니다.
Trilarion

1

@JasonBaker의 벤치 마크에서 영감을 얻은 다음은 10 개의 "abcdefghijklmnopqrstuvxyz"문자열을 비교하여 .join()더 빠름을 보여주는 간단한 것입니다. 이 작은 변수 증가에도 불구하고 :

카테 네이션

>>> x = timeit.Timer(stmt='"abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz"')
>>> x.timeit()
0.9828147209324385

붙다

>>> x = timeit.Timer(stmt='"".join(["abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz"])')
>>> x.timeit()
0.6114138159765048

이 질문에 대한 답을 찾으십시오 (오래로 스크롤) : stackoverflow.com/questions/1349311/…
mshsayem

1

A의 작은 세트짧은 문자열 (더 이상 몇 자 이하의 즉, 2 또는 3 문자열), 플러스 빠른 방법 아직도있다. 파이썬 2와 3에서 mkoistinen의 훌륭한 스크립트 사용하기

plus 2.679107467004 (100.00% as fast)
join 3.653773699996 (73.32% as fast)
form 6.594011374000 (40.63% as fast)
intp 4.568015249999 (58.65% as fast)

따라서 코드에서 많은 수의 개별 작은 연결을 수행하는 경우 속도가 중요한 경우 플러스가 선호되는 방법 입니다.


1

아마도 "Python 3.6의 새로운 f- 문자열"은 문자열을 연결하는 가장 효율적인 방법입니다.

% s 사용

>>> timeit.timeit("""name = "Some"
... age = 100
... '%s is %s.' % (name, age)""", number = 10000)
0.0029734770068898797

.format 사용

>>> timeit.timeit("""name = "Some"
... age = 100
... '{} is {}.'.format(name, age)""", number = 10000)
0.004015227983472869

f 사용

>>> timeit.timeit("""name = "Some"
... age = 100
... f'{name} is {age}.'""", number = 10000)
0.0019175919878762215

출처 : https://realpython.com/python-f-strings/

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.