'{0}'. Format() быстрее, чем str() и '{}'. Format(), используя IPython% timeit и в противном случае используя чистый Python

Итак, это вещь CPython, не совсем уверенная, что она имеет такое же поведение с другими реализациями.

Но '{0}'.format() быстрее, чем str() и '{}'.format(). Я отправляю результаты из Python 3.5.2, но, я пробовал его с Python 2.7.12, и тренд тот же.

%timeit q=['{0}'.format(i) for i in range(100, 100000, 100)]
%timeit q=[str(i) for i in range(100, 100000, 100)]
%timeit q=['{}'.format(i) for i in range(100, 100000, 100)]

1000 loops, best of 3: 231 µs per loop
1000 loops, best of 3: 298 µs per loop
1000 loops, best of 3: 434 µs per loop

Из docs в object.__str__(self)

Вызывается str(object) и встроенными функциями format() и print() для вычисления "неформального" или красиво печатаемого строкового представления объекта.

Итак, str() и format() вызывают тот же метод object.__str__(self), но откуда эта разница в скорости?

UPDATE как @StefanPochmann и @Leon отметили в комментариях, они получают разные результаты. Я попытался запустить его с помощью python -m timeit "...", и они правы, потому что результаты:

$ python3 -m timeit "['{0}'.format(i) for i in range(100, 100000, 100)]"
1000 loops, best of 3: 441 usec per loop

$ python3 -m timeit "[str(i) for i in range(100, 100000, 100)]"
1000 loops, best of 3: 297 usec per loop

$ python3 -m timeit "['{}'.format(i) for i in range(100, 100000, 100)]"
1000 loops, best of 3: 420 usec per loop

Итак, похоже, что IPython делает что-то странное...

НОВЫЙ ВОПРОС. Каков предпочтительный способ преобразования объекта в str по скорости?

Ответы

Ответ 1

Тайм-аут IPython по какой-то причине отключен (хотя при тестировании с более длинной строкой формата в разных ячейках он вел себя немного лучше). Возможно, выполнение в одних и тех же ячейках неверно, не знаю.

В любом случае "{}" бит быстрее, чем "{pos}", который быстрее, чем "{name}", в то время как все они медленнее, чем str.

str(val) - самый быстрый способ преобразования объекта в str; он прямо вызывает объекты __str__, если он существует, и возвращает результирующую строку. Другие, такие как format, (или str.format), включают дополнительные накладные расходы из-за дополнительного вызова функции (до самого format); обрабатывая любые аргументы, анализируя строку формата и затем вызывая __str__ их args.

Для методов str.format "{}" используется автоматическая нумерация; из небольшого раздела в docs в синтаксисе формата:

Изменено в версии 3.1: Спецификаторы позиционного аргумента могут быть опущены, поэтому '{} {}' эквивалентно '{0} {1}'.

то есть, если вы укажете строку формы:

"{}{}{}".format(1, 2, 3)

CPython сразу же знает, что это эквивалентно:

"{0}{1}{2}".format(1, 2, 3)

С строкой формата, содержащей цифры с указанием позиций; CPython не может принимать строго возрастающее число (которое начинается с 0) и должно анализировать каждую отдельную скобку, чтобы получить ее право, немного замедляя процесс:

"{1}{2}{0}".format(1, 2, 3)

То, почему это также не позволяет смешать эти два вместе:

"{1}{}{2}".format(1, 2, 3)

когда вы попытаетесь сделать это, вы получите хороший ValueError:

ValueError: cannot switch from automatic field numbering to manual field specification

он также захватывает эти позиционные элементы с PySequence_GetItem, которые, я уверен, являются быстрыми, по крайней мере, по сравнению с PyObject_GetItem [см. следующий].

Для значений "{name}" у CPython всегда есть дополнительная работа, потому что мы имеем дело с ключевыми словами, а не с позиционными; это включает в себя такие вещи, как построение словаря для вызовов и генерация более LOAD инструкций байтового кода для загрузки key и значений. Ключевая форма вызова функции всегда вводит некоторые накладные расходы. Кроме того, кажется, что захват фактически использует PyObject_GetItem, который несет некоторые дополнительные накладные расходы из-за его родового характера.