두 문자열을 연결하기 위해 '+'를 사용하지 않는 이유는 무엇입니까?


124

파이썬의 일반적인 반 패턴 +은 루프에서 사용하여 문자열 시퀀스를 연결하는 것 입니다. 파이썬 인터프리터가 각 반복마다 새로운 문자열 객체를 만들어야하고 결국 2 차 시간이 걸리기 때문에 이것은 나쁘다. (최신 버전의 CPython은 어떤 경우에는 분명히이를 최적화 할 수 있지만 다른 구현에서는 그렇게 할 수 없으므로 프로그래머는 이에 의존하지 않는 것이 좋습니다.) ''.join이 작업을 수행하는 올바른 방법입니다.

그러나 문자열 연결에는 절대 사용 하지 말고 대신 항상 또는 형식 문자열을 사용 해서는 안된다고 ( 여기 Stack Overflow 포함) 말했다고 들었습니다 . 두 개의 문자열 만 연결하는 경우 왜 이런 경우인지 이해할 수 없습니다. 내 이해가 정확하다면 2 차 시간이 걸리지 않아야 하며 나 보다 더 깨끗하고 읽기 쉽다고 생각 합니다 .+''.joina + b''.join((a, b))'%s%s' % (a, b)

+두 문자열을 연결 하는 데 사용 하는 것이 좋은 습관 입니까? 아니면 내가 모르는 문제가 있습니까?


깔끔하고 연결하지 않도록 더 많은 제어 권한이 있습니다. 하지만 약간 느린 스트링 배싱 트레이드 오프 : P
Jakob Bowyer

당신은 말을 +빠르게 또는 느리게합니까? 그리고 왜?
Taymon

1
+, 빠른 In [2]: %timeit "a"*80 + "b"*80 1000000 loops, best of 3: 356 ns per loop In [3]: %timeit "%s%s" % ("a"*80, "b"*80) 1000000 loops, best of 3: 907 ns per loop
야콥 보이어

4
In [3]: %timeit "%s%s" % (a, b) 1000000 loops, best of 3: 590 ns per loop In [4]: %timeit a + b 10000000 loops, best of 3: 147 ns per loop
Jakob Bowyer

1
@JakobBowyer 및 기타 : "문자열 연결이 나쁘다"인수는 속도와 거의 관련이 없지만 __str__. 예제는 내 대답을 참조하십시오.
Izkata 2012

답변:


120

합치에 아무것도 잘못이 두 가지 와 문자열 +. 실제로 ''.join([a, b]).

두 개 이상의 문자열을 연결 +하는 것은 O (n ^ 2) 연산 (O (n)과 비교하여 join)이므로 비효율적입니다. 그러나 이것은 루프 사용과 관련이 없습니다. 짝수 a + b + c + ...는 O (n ^ 2)이므로 각 연결이 새 문자열을 생성하기 때문입니다.

CPython2.4 이상은이를 완화하려고 시도하지만 join2 개 이상의 문자열을 연결할 때 사용하는 것이 좋습니다 .


5
@Mutant : .join반복 가능하므로 .join([a,b])및 둘 다 .join((a,b))유효합니다.
foundling

1
흥미로운 타이밍 은 CPython 2.3+의 경우에도 stackoverflow.com/a/12171382/378826(Lennart Regebro에서 ) 에서 사용 +하거나 +=수락 된 답변 (2013 년부터)에서 힌트를 제공하며, 이것이 명확하게 노출되는 경우 "추가 / 조인"패턴 만 선택했습니다. 문제 해결에 대한 아이디어.
Dilettant

49

Plus 연산자는 두 개의 Python 문자열 을 연결하는 완벽한 솔루션 입니다. 그러나 두 개 이상의 문자열을 계속 추가하는 경우 (n> 25) 다른 것을 생각할 수 있습니다.

''.join([a, b, c]) 트릭은 성능 최적화입니다.


2
튜플이 목록보다 낫지 않을까요?
ThiefMaster

7
튜플이 더 빠를 것입니다-코드는 단지 예일뿐입니다. :) 일반적으로 긴 다중 문자열 입력은 동적입니다.
Mikko Ohtamaa 2012

5
@martineau 나는 그가 append()문자열을 동적으로 생성하고 목록에 추가하는 것을 의미한다고 생각 합니다.
Peter C

5
여기서 말할 필요가 있습니다. 튜플은 특히 성장하는 경우 일반적으로 SLOWER 구조입니다. 목록을 사용하면 항목을 동적으로 연결할 때 훨씬 빠른 list.extend (list_of_items) 및 list.append (item)를 사용할 수 있습니다.
Antti Haapala

6
에 대한 +1 n > 25. 인간은 어딘가에서 시작하기 위해 참조 점이 필요합니다.
n611x007 2013

8

문자열 연결에 +를 사용해서는 안되며 대신 항상 ''.join을 사용하면 안된다는 가정은 신화 일 수 있습니다. 를 사용 +하면 불변의 문자열 객체의 불필요한 임시 복사본이 생성 되는 것은 사실 이지만 자주 인용되지 않는 다른 사실은 join루프에서 호출 하면 일반적으로 function call. 예를 들어 보겠습니다.

연결된 SO 질문에서 하나와 더 큰 조작으로 두 개의 목록을 만듭니다.

>>> myl1 = ['A','B','C','D','E','F']
>>> myl2=[chr(random.randint(65,90)) for i in range(0,10000)]

두 가지 기능을 만들 수 있습니다 UseJoinUsePlus각각의 사용 join+기능을.

>>> def UsePlus():
    return [myl[i] + myl[i + 1] for i in range(0,len(myl), 2)]

>>> def UseJoin():
    [''.join((myl[i],myl[i + 1])) for i in range(0,len(myl), 2)]

첫 번째 목록으로 timeit을 실행할 수 있습니다.

>>> myl=myl1
>>> t1=timeit.Timer("UsePlus()","from __main__ import UsePlus")
>>> t2=timeit.Timer("UseJoin()","from __main__ import UseJoin")
>>> print "%.2f usec/pass" % (1000000 * t1.timeit(number=100000)/100000)
2.48 usec/pass
>>> print "%.2f usec/pass" % (1000000 * t2.timeit(number=100000)/100000)
2.61 usec/pass
>>> 

런타임은 거의 동일합니다.

cProfile을 사용할 수 있습니다.

>>> myl=myl2
>>> cProfile.run("UsePlus()")
         5 function calls in 0.001 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.001    0.001    0.001    0.001 <pyshell#1376>:1(UsePlus)
        1    0.000    0.000    0.001    0.001 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {len}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.000    0.000    0.000    0.000 {range}


>>> cProfile.run("UseJoin()")
         5005 function calls in 0.029 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.015    0.015    0.029    0.029 <pyshell#1388>:1(UseJoin)
        1    0.000    0.000    0.029    0.029 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {len}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
     5000    0.014    0.000    0.014    0.000 {method 'join' of 'str' objects}
        1    0.000    0.000    0.000    0.000 {range}

그리고 Join을 사용하면 오버 헤드를 증가시킬 수있는 불필요한 함수 호출이 발생하는 것으로 보입니다.

이제 질문으로 돌아갑니다. 모든 경우 에 +over join를 사용하지 말아야합니까 ?

나는 생각하지 않는다.

  1. 문제의 문자열 길이
  2. 연결 작업이 없습니다.

그리고 개발 초기 최적화 과정에서 벗어나는 것은 악합니다.


7
물론, 아이디어는 join루프 자체 내부에서 사용 하는 것이 아니라 루프가 결합을 위해 전달되는 시퀀스를 생성하는 것입니다.
jsbueno 2012

7

여러 사람과 함께 작업 할 때 무슨 일이 일어나고 있는지 정확히 알기가 어려울 때가 있습니다. 연결 대신 형식 문자열을 사용하면 우리에게 여러 번 발생하는 특정 성가심을 피할 수 있습니다.

함수에 인수가 필요하고 문자열을받을 것으로 예상하여 작성합니다.

In [1]: def foo(zeta):
   ...:     print 'bar: ' + zeta

In [2]: foo('bang')
bar: bang

따라서이 함수는 코드 전체에서 꽤 자주 사용될 수 있습니다. 동료는 그것이 무엇을하는지 정확히 알 수 있지만 반드시 내부에 대해 완전히 최신 상태는 아니며 함수가 문자열을 기대한다는 것을 모를 수 있습니다. 그래서 그들은 다음과 같이 끝날 수 있습니다.

In [3]: foo(23)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

/home/izkata/<ipython console> in <module>()

/home/izkata/<ipython console> in foo(zeta)

TypeError: cannot concatenate 'str' and 'int' objects

형식 문자열을 사용했다면 문제가 없습니다.

In [1]: def foo(zeta):
   ...:     print 'bar: %s' % zeta
   ...:     
   ...:     

In [2]: foo('bang')
bar: bang

In [3]: foo(23)
bar: 23

을 정의하는 모든 유형의 객체에 대해서도 마찬가지이며 __str__전달 될 수 있습니다.

In [1]: from datetime import date

In [2]: zeta = date(2012, 4, 15)

In [3]: print 'bar: ' + zeta
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

/home/izkata/<ipython console> in <module>()

TypeError: cannot concatenate 'str' and 'datetime.date' objects

In [4]: print 'bar: %s' % zeta
bar: 2012-04-15

그렇습니다 : 형식 문자열을 사용할 수 있다면 그것을 수행하고 Python이 제공하는 것을 활용하십시오.


1
정당한 반대 의견에 +1. 나는 여전히 내가 선호한다고 +생각한다.
Taymon 2012

1
foo 메서드를 다음과 같이 정의하지 않는 이유는 무엇입니까? print 'bar :'+ str (zeta)?
EngineerWithJava54321

@ EngineerWithJava54321 예를 들어, 오류가 없는지 확인 zeta = u"a\xac\u1234\u20ac\U00008000"하려면 print 'bar: ' + unicode(zeta)을 사용해야 합니다. %s바로 그것에 대해 생각하지 않고 그것을 않습니다, 그리고 훨씬 짧은
Izkata

@ EngineerWithJava54321 다른 예제는 여기서 덜 관련성이 있지만 예를 들어 다른 언어 "bar: %s"로 번역 될 수 "zrb: %s br"있습니다. %s버전은 작동하지만 문자열 - CONCAT 버전은 모든 경우를 처리 할 수 엉망이 될 것하고 번역자 지금 처리하는 두 개의 별도의 번역 할 것이다
Izkata

foo의 구현이 무엇인지 모르는 경우 def.
insidesin

3

빠른 테스트를했습니다.

import sys

str = e = "a xxxxxxxxxx very xxxxxxxxxx long xxxxxxxxxx string xxxxxxxxxx\n"

for i in range(int(sys.argv[1])):
    str = str + e

시간을 정했습니다.

mslade@mickpc:/binks/micks/ruby/tests$ time python /binks/micks/junk/strings.py  8000000
8000000 times

real    0m2.165s
user    0m1.620s
sys     0m0.540s
mslade@mickpc:/binks/micks/ruby/tests$ time python /binks/micks/junk/strings.py  16000000
16000000 times

real    0m4.360s
user    0m3.480s
sys     0m0.870s

a = a + b케이스에 대한 최적화가 분명히 있습니다 . 예상대로 O (n ^ 2) 시간을 나타내지 않습니다.

따라서 적어도 성능면에서 사용하는 +것이 좋습니다.


3
여기에서 "join"사례와 비교할 수 있습니다. 그리고 등등 pypy, 자이 썬, IronPython의 같은 다른 파이썬 구현의 문제는 ...이
jsbueno

3

Python 문서에 따르면 str.join ()을 사용하면 다양한 Python 구현에서 성능 일관성을 얻을 수 있습니다. CPython은 s = s + t의 2 차 동작을 최적화하지만 다른 Python 구현에서는 그렇지 않을 수 있습니다.

CPython 구현 세부 정보 : s와 t가 모두 문자열 인 경우 CPython과 같은 일부 Python 구현은 일반적으로 s = s + t 또는 s + = t 형식의 할당에 대해 내부 최적화를 수행 할 수 있습니다. 적용 가능한 경우이 최적화는 2 차 런타임을 훨씬 적게 만듭니다. 이 최적화는 버전 및 구현에 따라 다릅니다. 성능에 민감한 코드의 경우 버전 및 구현에서 일관된 선형 연결 성능을 보장하는 str.join () 메서드를 사용하는 것이 좋습니다.

Python 문서의 시퀀스 유형 (각주 [6] 참조)



0

''.join ([a, b])+ 보다 나은 솔루션 입니다.

코드는 Python의 다른 구현 (PyPy, Jython, IronPython, Cython, Psyco 등)에 불리하지 않는 방식으로 작성되어야하기 때문입니다.

form a + = b 또는 a = a + b는 CPython에서도 깨지기 쉬우 며 refcounting을 사용하지 않는 구현에는 전혀 존재 하지 않습니다 (참조 카운트는 a에 대한 참조, 포인터 또는 핸들 수를 저장하는 기술 객체, 메모리 블록, 디스크 공간 또는 기타 리소스와 같은 리소스 )

https://www.python.org/dev/peps/pep-0008/#programming-recommendations


1
a += b모든 Python 구현에서 작동합니다. 일부 에서는 루프 내에서 수행 될 때 2 차 시간이 걸립니다 . 질문은 루프 외부 의 문자열 연결에 관한 것 입니다.
Taymon 2016 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.