Łączenie łańcuchów a zastępowanie łańcuchów w Pythonie

W Pythonie wymyka mi miejsce i czas użycia konkatenacji w stosunku do substytucji łańcuchów. Jako że konkatenacja strun odnotowała duży wzrost wydajności, czy jest to (coraz bardziej) decyzja stylistyczna, a nie praktyczna?

Dla konkretnego przykładu, jak należy poradzić sobie z budową elastycznych 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)

Edit: pojawiły się również sugestie dotyczące dołączenia listy łańcuchów i użycia nazwanego zastępowania. Są to warianty na temat główny, który sposób jest właściwy, aby to zrobić w którym czasie? Dzięki za odpowiedzi!

Author: gotgenes, 2008-12-18

9 answers

Konkatenacja jest (znacznie) szybsza według mojej maszyny. Ale stylistycznie jestem gotów zapłacić cenę zastępstwa, jeśli wydajność nie jest krytyczna. Cóż, a jeśli potrzebuję formatowania, nie ma potrzeby nawet zadawać pytania... nie ma innej opcji niż użycie interpolacji/szablonów.

>>> 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
 55
Author: Vinko Vrsalovic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-21 06:41:58

Nie zapomnij o podmianie nazw:

def so_question_uri_namedsub(q_num):
    return "%(domain)s%(questions)s/%(q_num)d" % locals()
 23
Author: too much php,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-18 00:22:59

Uważaj na łączenie strun w pętlę! koszt połączenia łańcuchów jest proporcjonalny do długości wyniku. Zapętlenie prowadzi cię prosto do krainy N-squared. Niektóre języki zoptymalizują konkatenację do ostatnio przydzielonego ciągu znaków, ale ryzykowne jest liczenie na to, że kompilator zoptymalizuje algorytm kwadratowy do liniowego. Najlepiej użyć prymitywnego (join?), który pobiera całą listę ciągów, wykonuje pojedynczą alokację i łączy je wszystkie w raz.

 12
Author: Norman Ramsey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-18 04:40:07

" jako, że konkatenacja strun odnotowała duże zwiększenie wydajności..."

Jeśli wydajność ma znaczenie, dobrze o tym wiedzieć.

Jednak problemy z wydajnością, które widziałem, nigdy nie sprowadzały się do operacji ciągów. Ogólnie mam problemy z I / O, sortowaniem i o (n2) operacje są wąskimi gardłami.

Dopóki operacje ciągów nie będą ogranicznikami wydajności, będę trzymał się rzeczy, które są oczywiste. Najczęściej jest to substytucja, gdy jest to jedna linia lub mniej, konkatenacja, gdy ma to sens, i narzędzie szablonu (jak Mako), gdy jest duże.

 11
Author: S.Lott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-18 00:18:13

To, co chcesz połączyć/interpolować i jak chcesz sformatować wynik, powinno napędzać twoją decyzję.

  • Interpolacja łańcuchów umożliwia łatwe dodawanie formatowania. W rzeczywistości, twoja wersja interpolacji łańcuchów nie robi tego samego co wersja konkatenacji; w rzeczywistości dodaje dodatkowy ukośnik przed parametrem q_num. Aby zrobić to samo, musisz napisać return DOMAIN + QUESTIONS + "/" + str(q_num) w tym przykładzie.

  • Interpolacja ułatwia formatowanie cyfry; "%d of %d (%2.2f%%)" % (current, total, total/current) byłyby znacznie mniej czytelne w formie konkatenacji.

  • Konkatenacja jest przydatna, gdy nie ma ustalonej liczby elementów do String-ize.

Wiedz również, że Python 2.6 wprowadza nową wersję interpolacji łańcuchów, zwaną string template:

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

String template ma w końcu zastąpić % - interpolację, ale myślę, że to nie nastąpi przez dłuższy czas.

 10
Author: Tim Lesher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-18 00:18:50

Z ciekawości testowałem szybkość różnych metod łączenia/zastępowania łańcuchów. Wyszukiwarka Google na ten temat mnie tu przywiozła. Pomyślałem, że opublikuję wyniki badań w nadziei, że to pomoże komuś zdecydować.

    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)

...Po uruchomieniu runtests((percent_, format_, format2_, concat_), runs=5), odkryłem, że metoda % była około dwa razy szybsza niż pozostałe na tych małych strunach. Metoda concat była zawsze najwolniejsza (ledwo). Były bardzo małe różnice podczas przełączania pozycji w format() metoda, ale zmiana pozycji była zawsze co najmniej .01 wolniejszy od zwykłej metody formatowania.

Przykładowe wyniki badań:

    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)

Uruchomiłem je, ponieważ używam konkatenacji łańcuchów w moich skryptach i zastanawiałem się, jaki był koszt. Sprawdzałem je w różnych kolejnościach, aby upewnić się, że nic nie przeszkadzało, lub uzyskiwałem lepsze wyniki jako pierwszy lub ostatni. Na marginesie dodałem do tych funkcji jakieś dłuższe Generatory strun jak "%s" + ("a" * 1024) i zwykły concat był prawie 3 razy tak szybko (1,1 vs 2,8) jak przy użyciu metod format i %. Myślę, że to zależy od strun i tego, co próbujesz osiągnąć. Jeśli wydajność naprawdę ma znaczenie, może lepiej wypróbować różne rzeczy i przetestować je. Mam tendencję do wyboru czytelności nad prędkością, chyba że prędkość staje się problemem, ale to tylko ja. Więc nie podoba mi się mój kopiuj / wklej, musiałem umieścić 8 spacji na wszystko, aby wyglądało dobrze. Zwykle używam 4.

 7
Author: Cj Welborn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-13 03:27:35

Pamiętaj, decyzje stylistyczne są decyzjami praktycznymi, jeśli kiedykolwiek planujesz utrzymać lub debugować swój kod :-) jest słynny cytat z Knutha (może cytując Hoare?): "Powinniśmy zapomnieć o małej wydajności, powiedzmy około 97% czasu: przedwczesna optymalizacja jest korzeniem wszelkiego zła."

Dopóki jesteś ostrożny, aby (powiedzieć) nie zamienić zadania O (n) W O (n2) zadanie, wybrałbym to, co jest dla Ciebie najłatwiejsze do zrozumienia..

 4
Author: John Fouhy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-18 01:04:35

Używam substytucji, gdzie tylko mogę. Używam konkatenacji tylko wtedy, gdy buduję ciąg w pętli typu for.

 0
Author: Draemon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-17 23:56:12

Właściwie właściwą rzeczą do zrobienia ,w tym przypadku (budowanie ścieżek) jest użycie os.path.join. Nie concatenation string lub interpolation

 -1
Author: hoskeri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-20 03:56:37