Конкатенация строк и подстановка строк в 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)

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

вопрос задан 17.12.2008
gotgenes
20463 репутация

9 ответов


  • 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
    
    ответ дан Vinko Vrsalovic, с репутацией 199051, 18.12.2008
  • 23 рейтинг

    Не забудьте о именной замене:

    def so_question_uri_namedsub(q_num):
        return "%(domain)s%(questions)s/%(q_num)d" % locals()
    
    ответ дан too much php, с репутацией 54894, 18.12.2008
  • 12 рейтинг

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

    ответ дан Norman Ramsey, с репутацией 161143, 18.12.2008
  • 11 рейтинг

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

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

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

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

    ответ дан S.Lott, с репутацией 309793, 18.12.2008
  • 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)
    

    Шаблонирование строк должно в конечном итоге заменить% -интерполяцию, но я думаю, что этого не произойдет в течение достаточно долгого времени.

    ответ дан Tim Lesher, с репутацией 4816, 18.12.2008
  • 7 рейтинг

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

    ответ дан Cj Welborn, с репутацией 101, 13.05.2013
  • 4 рейтинг

    Помните, стилистические решения - это практических решений, если вы когда-либо планируете поддерживать или отлаживать свой код :-) Есть известная цитата из Кнута (возможно, цитирующая Hoare? ): «Мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация - корень всех зол. "

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

    ответ дан John Fouhy, с репутацией 28989, 18.12.2008
  • 0 рейтинг

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

    ответ дан Draemon, с репутацией 25666, 17.12.2008
  • -1 рейтинг

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

    ответ дан hoskeri, с репутацией 329, 20.08.2011