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


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

Edit: также были предложения о присоединении к списку строк и использовании именованной подстановки. Это варианты на центральная тема, которая заключается в том, какой способ является правильным способом сделать это в какое время? Спасибо за ответы!

9 98

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) операции являются узкими местами.

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

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

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

Я просто проверял скорость различных методов конкатенации/замены строк из любопытства. Поиск в 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.

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