파이썬에서 문자열 연결과 문자열 대체


98

Python에서는 문자열 연결과 문자열 대체를 사용하는 위치와시기를 알 수 없습니다. 문자열 연결로 인해 성능이 크게 향상되었으므로 이것은 실용적인 결정이 아닌 문체 결정입니까?

구체적인 예를 들어, 유연한 URI 구성을 어떻게 처리해야합니까?

DOMAIN = 'http://stackoverflow.com'
QUESTIONS = '/questions'

def so_question_uri_sub(q_num):
    return "%s%s/%d" % (DOMAIN, QUESTIONS, q_num)

def so_question_uri_cat(q_num):
    return DOMAIN + QUESTIONS + '/' + str(q_num)

편집 : 문자열 목록 결합 및 명명 된 대체 사용에 대한 제안도 있습니다. 이것들은 중심 주제에 대한 변형입니다. 즉, 어떤 방법으로 어느 시점에이를 수행 할 수 있습니까? 응답 해 주셔서 감사합니다!


루비에서는 문자열 보간이 일반적으로 연결보다 빠릅니다 ...
Keltia

return "".join ([DOMAIN, QUESTIONS, str (q_num)])을 잊어 버렸습니다
Jimmy

저는 Ruby 전문가는 아니지만 Ruby에서 문자열이 변경 가능하기 때문에 보간이 더 빠르다고 확신합니다. 문자열은 Python에서 변경할 수없는 시퀀스입니다.
gotgenes

1
URI에 대한 약간의 설명입니다. URI는 문자열과 정확히 같지 않습니다. URI가 있으므로 연결하거나 비교할 때 매우주의해야합니다. 예 : 포트 80에서 http를 통해 표현을 전달하는 서버. example.org (끝에 slah 없음) example.org/ (슬래시) example.org:80/(slah+port 80)은 동일한 URI이지만 동일하지는 않습니다. 끈.
karlcow 2010-07-05

답변:


55

내 컴퓨터에 따르면 연결이 (상당히) 빠릅니다. 하지만 스타일 상으로는 성능이 중요하지 않은 경우 대체 비용을 지불 할 의향이 있습니다. 음, 서식이 필요한 경우 질문도 할 필요가 없습니다. 보간 / 템플릿을 사용하는 것 외에는 옵션이 없습니다.

>>> import timeit
>>> def so_q_sub(n):
...  return "%s%s/%d" % (DOMAIN, QUESTIONS, n)
...
>>> so_q_sub(1000)
'http://stackoverflow.com/questions/1000'
>>> def so_q_cat(n):
...  return DOMAIN + QUESTIONS + '/' + str(n)
...
>>> so_q_cat(1000)
'http://stackoverflow.com/questions/1000'
>>> t1 = timeit.Timer('so_q_sub(1000)','from __main__ import so_q_sub')
>>> t2 = timeit.Timer('so_q_cat(1000)','from __main__ import so_q_cat')
>>> t1.timeit(number=10000000)
12.166618871951641
>>> t2.timeit(number=10000000)
5.7813972166853773
>>> t1.timeit(number=1)
1.103492206766532e-05
>>> t2.timeit(number=1)
8.5206360154188587e-06

>>> def so_q_tmp(n):
...  return "{d}{q}/{n}".format(d=DOMAIN,q=QUESTIONS,n=n)
...
>>> so_q_tmp(1000)
'http://stackoverflow.com/questions/1000'
>>> t3= timeit.Timer('so_q_tmp(1000)','from __main__ import so_q_tmp')
>>> t3.timeit(number=10000000)
14.564135316080637

>>> def so_q_join(n):
...  return ''.join([DOMAIN,QUESTIONS,'/',str(n)])
...
>>> so_q_join(1000)
'http://stackoverflow.com/questions/1000'
>>> t4= timeit.Timer('so_q_join(1000)','from __main__ import so_q_join')
>>> t4.timeit(number=10000000)
9.4431309007150048

10
실제 큰 문자열 (예 : 100,000 자)로 테스트 했습니까?
drnk apr

24

명명 된 대체를 잊지 마세요.

def so_question_uri_namedsub(q_num):
    return "%(domain)s%(questions)s/%(q_num)d" % locals()

4
이 코드에는 적어도 두 가지 잘못된 프로그래밍 관행이 있습니다. 전역 변수 (도메인 및 질문은 함수 내에서 선언되지 않음)에 대한 기대와 필요한 것보다 많은 변수를 format () 함수에 전달하는 것입니다. 이 답변은 나쁜 코딩 관행을 가르치기 때문에 반대 투표합니다.
jperelli

12

루프에서 문자열을 연결하지 않도록주의하십시오! 문자열 연결 비용은 결과 길이에 비례합니다. 루핑은 N 제곱의 땅으로 곧바로 이어집니다. 일부 언어는 가장 최근에 할당 된 문자열에 대한 연결을 최적화하지만 컴파일러에 의존하여 2 차 알고리즘을 선형으로 최적화하는 것은 위험합니다. join전체 문자열 목록을 취하고 단일 할당을 수행하며 한 번에 모두 연결 하는 기본 ( ?) 을 사용하는 것이 가장 좋습니다 .


16
그것은 현재가 아닙니다. 최신 버전의 Python에서는 루프에서 문자열을 연결할 때 숨겨진 문자열 버퍼가 생성됩니다.
Seun Osewa

5
@Seun : 네, 제가 말했듯이 일부 언어는 최적화되지만 위험한 관행입니다.
Norman Ramsey

11

"문자열 연결로 성능이 크게 향상 되었기 때문에 ..."

성능이 중요하다면 알아두면 좋습니다.

그러나 내가 본 성능 문제는 문자열 연산으로 내려온 적이 없습니다. 나는 일반적으로 I / O, 정렬 및 O ( n 2 ) 작업이 병목 현상이되는 문제에 봉착했습니다 .

문자열 연산이 성능 제한이 될 때까지 나는 명백한 것을 고수 할 것입니다. 대부분은 한 줄 이하인 경우 대체, 의미가있는 경우 연결, 큰 경우 템플릿 도구 (예 : Mako)입니다.


10

연결 / 보간하려는 항목과 결과 형식을 지정하는 방법이 결정을 좌우해야합니다.

  • 문자열 보간을 사용하면 서식을 쉽게 추가 할 수 있습니다. 사실, 문자열 보간 버전은 연결 버전과 동일한 작업을 수행하지 않습니다. 실제로 q_num매개 변수 앞에 추가 슬래시를 추가합니다 . 동일한 작업을 수행하려면 return DOMAIN + QUESTIONS + "/" + str(q_num)해당 예제 를 작성해야합니다 .

  • 보간을 사용하면 숫자 형식을보다 쉽게 ​​지정할 수 있습니다. "%d of %d (%2.2f%%)" % (current, total, total/current)연결 형식에서는 가독성이 훨씬 떨어집니다.

  • 연결은 문자열화할 고정 된 수의 항목이 없을 때 유용합니다.

또한 Python 2.6에는 문자열 템플릿 이라는 새로운 버전의 문자열 보간이 도입되었습니다 .

def so_question_uri_template(q_num):
    return "{domain}/{questions}/{num}".format(domain=DOMAIN,
                                               questions=QUESTIONS,
                                               num=q_num)

문자열 템플릿은 결국 %-보간법을 대체 할 예정이지만 꽤 오랫동안 일어나지 않을 것이라고 생각합니다.


글쎄, 파이썬 3.0으로 이동하기로 결정할 때마다 발생합니다. 또한 어쨌든 % 연산자를 사용하여 명명 된 대체를 수행 할 수 있다는 사실에 대해서는 Peter의 설명을 참조하십시오.
John Fouhy

"연결은 문자열화할 고정 된 수의 항목이 없을 때 유용합니다." -목록 / 배열을 의미합니까? 이 경우 그냥 join () 할 수 없습니까?
strager

"그냥 가입 할 수 없나요?" -예 (항목 사이에 균일 한 구분 기호를 원한다고 가정). 목록 및 생성기 이해는 string.join과 잘 작동합니다.
Tim Lesher

1
"글쎄, 파이썬 3.0으로 이동하기로 결정할 때마다 일어날 것입니다."-아니요, py3k는 여전히 % 연산자를 지원합니다. 다음으로 가능한 지원 중단 지점은 3.1이므로 여전히 수명이 있습니다.
Tim Lesher

2
2 년 후 ... python 3.2가 출시 될 예정이며 % 스타일 보간은 여전히 ​​괜찮습니다.
Corey Goldberg 2011 년

8

저는 호기심에서 다른 문자열 연결 / 대체 방법의 속도를 테스트하고있었습니다. 주제에 대한 Google 검색이 나를 여기로 데려 왔습니다. 누군가가 결정하는 데 도움이 될 수 있기를 바라며 테스트 결과를 게시 할 것이라고 생각했습니다.

    import timeit
    def percent_():
            return "test %s, with number %s" % (1,2)

    def format_():
            return "test {}, with number {}".format(1,2)

    def format2_():
            return "test {1}, with number {0}".format(2,1)

    def concat_():
            return "test " + str(1) + ", with number " + str(2)

    def dotimers(func_list):
            # runs a single test for all functions in the list
            for func in func_list:
                    tmr = timeit.Timer(func)
                    res = tmr.timeit()
                    print "test " + func.func_name + ": " + str(res)

    def runtests(func_list, runs=5):
            # runs multiple tests for all functions in the list
            for i in range(runs):
                    print "----------- TEST #" + str(i + 1)
                    dotimers(func_list)

... 실행 한 후 runtests((percent_, format_, format2_, concat_), runs=5)% 메서드가이 작은 문자열에서 다른 메서드보다 약 두 배 빠르다는 것을 발견했습니다. concat 메서드는 항상 가장 느 렸습니다 (간신히). format()방법 에서 위치를 전환 할 때 매우 작은 차이가 있었지만 위치 전환은 항상 일반 형식 방법보다 .01 이상 느 렸습니다.

테스트 결과 샘플 :

    test concat_()  : 0.62  (0.61 to 0.63)
    test format_()  : 0.56  (consistently 0.56)
    test format2_() : 0.58  (0.57 to 0.59)
    test percent_() : 0.34  (0.33 to 0.35)

스크립트에서 문자열 연결을 사용하기 때문에 이것을 실행했으며 비용이 얼마인지 궁금했습니다. 방해가되지 않는지 확인하기 위해 다른 순서로 실행했거나 더 나은 성능을 처음 또는 마지막으로 얻었습니다. 참고로, 나는 더 긴 문자열 생성기를 같은 함수에 넣었고 "%s" + ("a" * 1024)일반 연결은 format%메서드 를 사용하는 것보다 거의 3 배 (1.1 대 2.8) 빠릅니다 . 나는 그것이 문자열과 당신이 달성하려는 것에 달려 있다고 생각합니다. 성능이 정말로 중요하다면 다른 것을 시도하고 테스트하는 것이 좋습니다. 속도가 문제가되지 않는 한, 나는 속도보다 가독성을 선택하는 경향이 있습니다. 그래서 복사 / 붙여 넣기가 마음에 들지 않았고, 제대로 보이도록 모든 것에 8 개의 공백을 넣어야했습니다. 저는 보통 4를 사용합니다.


1
어떻게 프로파일 링하는지 진지하게 고려해야합니다. 하나의 경우 두 개의 str 캐스트가 있기 때문에 concat이 느립니다. 문자열의 경우 결과는 반대입니다. 문자열 연결은 실제로 세 문자열 만 고려할 때 모든 대안보다 빠르기 때문입니다.
Justus Wingert 2015 년

@JustusWingert, 이제 2 년이되었습니다. 이 '테스트'를 게시 한 이후로 많은 것을 배웠습니다. 솔직히, 내가 사용하는 요즘 str.format()str.join() 일반 연결을 통해. 최근에 승인 된 PEP 498의 'f-strings'도 주시하고 있습니다. str()성능에 영향을 미치는 전화에 관해서는 당신이 옳다고 확신합니다. 당시에는 함수 호출이 얼마나 비싼 지 전혀 몰랐습니다. 나는 여전히 의심이있을 때 테스트를해야한다고 생각한다.
Cj Welborn 2015 년

을 사용한 빠른 테스트 후 백분율보다 느린 join_(): return ''.join(["test ", str(1), ", with number ", str(2)])것 같습니다 join.
gaborous

4

코드를 유지하거나 디버깅 할 계획이 있다면 스타일 결정 실용적인 결정이라는 점을 기억하십시오 . 조기 최적화는 모든 악의 근원입니다. "

O (n) 작업을 O (n 2 ) 작업으로 바꾸지 않도록주의하는 한 , 이해하기 가장 쉬운 방법을 사용하겠습니다.


0

가능한 한 대체를 사용합니다. for 루프에서 문자열을 작성하는 경우에만 연결을 사용합니다.


7
"for-loop에서 문자열 작성"– 종종 ''.join 및 생성기 표현식을 사용할 수있는 경우입니다.
John Fouhy

-1

실제로해야 할 올바른 일은이 경우 (빌딩 경로)를 사용하는 것 os.path.join입니다. 문자열 연결 또는 보간 아님


1
OS 경로 (파일 시스템과 같은)에는 해당되지만이 예제에서와 같이 URI를 구성 할 때는 그렇지 않습니다. URI에는 항상 구분 기호로 '/'가 있습니다.
Andre Blum
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.