Конкатенация строк и подстановка строк в Python - PullRequest
98 голосов
/ 18 декабря 2008

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

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

Ответы [ 9 ]

55 голосов
/ 18 декабря 2008

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

>>> 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
23 голосов
/ 18 декабря 2008

Не забудьте о именной подстановке:

def so_question_uri_namedsub(q_num):
    return "%(domain)s%(questions)s/%(q_num)d" % locals()
12 голосов
/ 18 декабря 2008

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

11 голосов
/ 18 декабря 2008

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

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

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

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

10 голосов
/ 18 декабря 2008

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

  • Строковая интерполяция позволяет легко добавлять форматирование. Фактически, ваша версия строковой интерполяции не делает то же самое, что и ваша версия конкатенации; на самом деле он добавляет дополнительную косую черту перед параметром 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)

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

7 голосов
/ 13 мая 2013

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

4 голосов
/ 18 декабря 2008

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

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

0 голосов
/ 20 августа 2011

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

0 голосов
/ 18 декабря 2008

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

...