Конкатенация строк и подстановка строк в Python

в Python, где и когда использование конкатенации строк против подстановки строк ускользает от меня. Поскольку конкатенация строк увидела большие повышения производительности, является ли это (становится больше) стилистическим решением, а не практическим?

для конкретного примера, как следует обрабатывать конструкцию гибких URIs:

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: также были предложения о присоединении к списку строк и использовании именованной подстановки. Это варианты центральная тема, которая заключается в том, какой путь является правильным способом сделать это в какое время? Спасибо за ответы!

9 ответов


конкатенация (значительно) быстрее в соответствии с моей машиной. Но стилистически я готов заплатить цену замены, если производительность не критична. Ну, и если мне нужно форматирование, нет необходимости даже задавать вопрос... нет другого выбора, кроме как использовать интерполяцию / шаблон.

>>> 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

Не забывайте о им замену:

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

будьте осторожны с объединением строк в цикле! стоимость конкатенации строк пропорциональна длине результата. Петля ведет вас прямо в страну N-квадрат. Некоторые языки оптимизируют конкатенацию к последней выделенной строке, но рискованно рассчитывать на компилятор для оптимизации вашего квадратичного алгоритма до линейного. Лучше всего использовать примитив (join?), который принимает весь список строк, выполняет одно выделение и конкатенирует все в один присест.


" поскольку конкатенация строк увидела большие повышения производительности..."

Если производительность имеет значение, это хорошо знать.

однако проблемы с производительностью, которые я видел, никогда не сводились к строковым операциям. У меня вообще возникли проблемы с вводом-выводом, сортировкой и O (n2) операции являются узкими местами.

пока строковые операции не станут ограничителями производительности, я буду придерживаться очевидных вещей. В основном, это подстановка, когда это одна строка или меньше, конкатенация, когда это имеет смысл, и инструмент шаблона (например, Мако), когда он большой.


то, что вы хотите объединить/интерполировать и как вы хотите отформатировать результат, должно управлять вашим решением.

  • интерполяция строк позволяет легко добавлять форматирование. Фактически, ваша версия интерполяции строк не делает то же самое, что и ваша версия конкатенации; она фактически добавляет дополнительную косую черту перед


Я просто тестировал скорость различных методов конкатенации/подстановки строк из любопытства. Поиск в 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) и регулярный конкат был почти в 3 раза быстрее (1.1 против 2.8), чем использование format и % методы. Я думаю, это зависит от струн и того, чего вы пытаетесь достичь. Если производительность действительно имеет значение, может быть, лучше попробовать разные вещи и проверить их. Я склонен выбирать читаемость над скоростью, если скорость не становится проблемой, но это только я. Так что мне не понравилась моя копия / вставка, мне пришлось поставить 8 пробелов на все, чтобы это выглядело правильно. Я обычно использую 4.


помните, что стилистические решения are практические решения, если вы когда-либо планируете поддерживать или отлаживать свой код: -) есть знаменитая цитата из кнута (возможно, цитируя Хоара?): "Мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация-это корень всего зла."

пока вы осторожны, чтобы (скажем) не превратить задачу O(n) в O(n2) задача, я бы пошел с тем, что вам легче всего понять..


Я использую замену везде, где могу. Я использую конкатенацию, только если я создаю строку, скажем, в for-loop.


на самом деле правильная вещь, в этом случае (построение путей) - использовать os.path.join. Не конкатенация строк или интерполяция