태그 보관물: anti-patterns

anti-patterns

두 문자열을 연결하기 위해 ‘+’를 사용하지 않는 이유는 무엇입니까? 문자열 시퀀스를 연결하는 것 입니다.

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

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

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



답변

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

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

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


답변

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

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


답변

문자열 연결에 +를 사용해서는 안되며 대신 항상 ”.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. 연결 작업이 없습니다.

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


답변

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

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

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이 제공하는 것을 활용하십시오.


답변

빠른 테스트를했습니다.

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) 시간을 나타내지 않습니다.

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


답변

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] 참조)


답변

파이썬 3.8에서 다음을 사용합니다.

string4 = f'{string1}{string2}{string3}'