Пользователь
0,0
рейтинг
12 сентября 2014 в 17:35

Разработка → Учим старую собаку новым трюкам или как я научился любить str.format и отказался от % перевод

Предлагаю вниманию читателей хабры и поклонникам python перевод довольно объемной статьи о форматировании строк. Сказка будет правдивой и намек в ней будет на то, что консерваторам стоит иногда рассматривать что-то новое, даже если привычка упорно сопротивляется.

Предугадывая любознательность читателей, склонных задавать вопросы не по теме разговора, скажу, что картинка имеет опосредованное отношение к python, правда не самое приятное. Предлагаю найти почему в качестве домашнего задания.

Жду замечания по ошибкам в оформлении и опечаткам в личку — с меня традиционные хабраплюшки.


Далее слова автора оригинальной статьи:

Я уже много лет пишу на python. Но в самом начале этого пути мне было интересно узнать как форматировать строки в стиле Perl. Напомню, что Perl (и многие интерпретаторы командной строки в Unix) поддерживают два типа строковых литералов — с одинарными кавычками (когда строка выводится как есть), и двойными где на место переменных подставляются их значения. В Perl, например, можно написать что то вроде:

$name = 'Reuven';
print "Hello, $name\n";

И программа, соответственно, напишет «Hello, Reuven».

Строковые литералы в python не зависят от типа кавычек и переменные в них никогда не разворачиваются в свои значения. Чтобы этого добиться традиционно использовался оператор % для строк. В этом контексте оператор смотрит на строку слева от себя и подсчитывает сколько значений нужно заменить на значения соответствующих переменных справа от себя. Результатом операции является новая строка со вставленными на место плейсхолдеров значениями переменных. Например:

>>> name = 'Reuven'
>>> "Hello, %s" % name

'Hello, Reuven'

Этот код на python вполне себе работает и выводит персонализированное приветствие. Так, несмотря на мою многолетнюю практику с python — я был вполне удовлетворен применением этого синтаксиса. Да, он не очень приятный и нет, я никогда не держал в памяти гору модификаторов printf, которые влияют на форматирование. В смысле я всегда использовал модификатор 's' (выводить как строку) и мне было достаточно того, что python неявно приводил аргументы к строке.

Но в данный момент факт, что синтаксис % подлежит списанию или, по крайней мере, объявлен устаревшим. В списке рассылки python-dev есть замечание, что в ветке 2.x он проживет минимум до 2022 года, но ничего не сказано про ветку 3.x, так что поддержка этого синтаксиса будет скоро удалена и применять его нежелательно. На смену ему пришел метод str.format.

В своих уроках по python я всегда упоминал о str.format, но в конкретных примерах чаще все полагался все-таки на %. Я даже рекомендовал студентам использовать % так как лично мне он казался намного проще.

Но стойкое ощущение того, что, я делаю что-то не так и, возможно, даже ввожу в заблуждение своих студентов подвигло меня поближе изучить str.format. В ходе исследования, я пришел к следующим выводам: 1) Он ничуть не сложнее % и даже проще в некоторых вариантах применения; 2) Я никогда не применял возможности str.format в полной мере, а они очень удобные, несмотря на некоторое время необходимое для их изучения.

Начнем с простейшего. Скажем кому-нибудь «Good morning», причем обратимся по имени и фамилии, предполагая что они сохранены в переменных «first» и «last». По-старому мы сделали бы так:

>>> first = 'Reuven'
>>> last = 'Lerner'
>>> "Good morning, %s %s" % (first, last)

'Good morning, Reuven Lerner'

Даже в таком примере мы сталкиваемся с одной из проблем %-синтаксиса — у нас теперь две переменных, и чтобы использовать их обе нам нужно сделать из них кортеж. С точки зрения python это логично, но, уверяю вас, очень многих студентов это очень удивляет.

Как этот пример будет выглядеть в случае str.format? Довольно похоже:

>>> "Good morning, {} {}".format(first, last)

'Good morning, Reuven Lerner'

Прошу обратить внимание, что мы немного поменяли принцип. Теперь это не бинарный оператор над строками, а метод объекта строка, принимающий ряд параметров. Это логично и более консистентно. Для тех же студентов оператор % в моих примерах выглядел как дополнение к print, а не операция над строками. Нотация с ".format" после строки делает более очевидным факт того, что это метод относящийся именно к этой строке.

Как вы уже наверняка знаете, вхождения “{} {}” в строке говорят что str.format должен принимать два параметра, значения которых будут вставлены в строку в том порядке, в котором они будут переданы в метод. Аргумента два, поэтому в строке должно быть два вхождения {}. Это немного сложнее понять, так как фигурные скобочки в Python намекают людям на словари и пустые скобочки выглядят не очень приятно. Но это ладно, я вполне могу с этим жить и принял это достаточно легко.

Момент, в котором str.format показывает первое преимущество над % — это при необходимости использования параметров в обратном порядке. На самом деле, с %s этого вообще никак не достичь. Невозможно также использовать значение одной переменной несколько раз. При использовании str.format мы вполне можем поменять последовательность подстановки:

>>> "Good morning, {1} {0}".format(first, last)

'Good morning, Lerner Reuven'

Обратите внимание, что если бы я использовал пустые скобочки “{} {}”, то подстановка произошла бы в том же порядке, в каком передаются в метод параметры. Можно представить себе параметры как индексируемую с нуля последовательность и если я хочу поменять порядок следования, то просто проставляю в фигурных скобочках нужные индексы этой последовательности. Самый первый наш пример с str.format можно записать и так:

>>> "Good morning, {0} {1}".format(first, last)

'Good morning, Reuven Lerner'

Заметим, что явно указав индексы, мы уже не можем положиться на автоматическую индексацию.

Разумеется, можно использовать последовательность и из списка, воспользовавшись оператором *:

>>> names = ('Reuven', 'Lerner')
>>> "Good morning, {} {}".format(*names)

'Good morning, Reuven Lerner'

Можно использовать и именованные аргументы:

>>> "Good morning, {first} {last}".format(first='Reuven', last='Lerner')

'Good morning, Reuven Lerner'

Этот вариант мне особенно нравится. Именованные параметры более явные (если у них хорошие имена), и применение {first} и {last} достаточно читабельно — особенно в сравнении с %(first)s, которое необходимо с оператором %

Именованные параметры можно, также, развернуть из словаря, используя оператор **:

>>> person = {'first':'Reuven', 'last':'Lerner'}
>>> "Good morning, {first} {last}".format(**person)

'Good morning, Reuven Lerner'

Я описал все это своим студентам и был достаточно удивлен тем насколько комфортнее им живется с таким синтаксисом. Да и самому стало приятнее работать.

Нужно упомянуть, что именованные и позиционные аргументы технически можно использовать совместно. Но лучше этого не делать:

>>> person = {'first':'Reuven', 'last':'Lerner'}
>>> "Good {0}, {first} {last}".format('morning', **person)

'Good morning, Reuven Lerner'


Я предупредил.

Чего может не хватать в str.format, так это… гм… форматирования. Плохая новость — в str.format совершенно другие правила определения того как форматировать вывод. Хорошая новость — эти правила достаточно несложно изучить и понять.

Снова начнем с простого: если нужно вывести строку заданной длины, то после имени переменной добавляем двоеточие (:) и затем количество символов. Так, чтобы вывести мое имя и дополнить его до десяти символов пробелами я должен делать так:

>>> "Your name is {name:10}".format(name="Reuven")

'Your name is Reuven    '

(Обратите внимание что строка дополнена пробелами после имени.)

Если нужно задать выравнивание по правой стороне блока — используется знак > между: и числом:

>>> "Your name is {name:>10}".format(name="Reuven")

'Your name is     Reuven'

И да, можно явно указать что я хочу выравнивания по левой стороне с помощью знака <
Если нужно вывести значение в центре блока, то вместо < и > используется символ ^:

>>> "Your name is {name:*^10}".format(name="Reuven")

'Your name is **Reuven**'

С текстом более менее понятно, но что насчет чисел? Лично мне было трудно предположить как это будет работать, но все оказалось достаточно прямолинейно. Для простого вывода чисел используем синтаксис похожий на строки:

>>> "The price is ${number}.".format(number=123)

'The price is $123.'

Но для чисел применяется большее количество модификаторов, чем для строк. Например, чтобы вывести число в двоичном виде добавляем модификатор «b», если в шестнадцатеричном — модификатор «x»:

>>> "The price is ${number:b}.".format(number=5)

'The price is $101.'

>>> "The price is ${number:x}.".format(number=123)

'The price is $7b.'


Разумеется, запись числа можно дополнить лидирующими нулями:

>>> "Your call is important to us. You are call #{number:05}.".format(number=123)

'Your call is important to us. You are call #00123.'

Заметим, что внутри {} нельзя использовать исполняемый python-код — вместо этого предлагается простенький микроязык отдельный и отличающийся от python в целом. Есть и небольшие исключения. Во-первых можно получить значения атрибутов/свойств через точку, во-вторых получить значение объекта по индексу, используя [].

Например:

>>> class Foo(object):
        def __init__(self):
        self.x = 100
>>> f = Foo()
>>> 'Your number is {o.x}'.format(o=f)

'Your number is 100'n

Мы получили атрибут «х» объекта «f». Этот объект доступен по имени «o» внутри строки. Получить атрибут можно, а вот выполнить его — нет:

>>> "Your name is {name.upper()}".format(name="Reuven")

AttributeError: 'str' object has no attribute 'upper()'

Я пытался выполнить “name.upper()”, предполагая, что будет вызван соответствующий метод, но python не разрешает выполнять код в этом месте и расценивает «upper()» как атрибут вместе со скобками. Без скобок вы получите просто строковое представление функции/метода:

>>> "Your name is {name.upper}".format(name="Reuven")

'Your name is <built-in method upper of str object at 0x1028bf2a0>'

С помощью квадратных скобок можно взять элемент итерируемого объекта (списка, строки) по индексу. Но операции разрезания (slice) не поддерживаются:

>>> "Your favorite number is {n[3]}.".format(n=numbers)

'Your favorite number is 3.'

Но:

>>> "Your favorite numbers are {n[2:4]}.".format(n=numbers)

ValueError: Missing ']' in format string

Можно использовать [] и для получения записей в словаре по имени, но имя вводится без кавычек:

>>> person = {'first':'Reuven', 'last':'Lerner'}
>>> "Your name is {p[first]}.".format(p=person)

'Your name is Reuven.'

При попытке использовать кавычки получим исключение…

>>> "Your name is {p['first']}.".format(p=person)

KeyError: "'first'"

Здесь приведены не все варианты использования str.format — на деле для каждого типа есть спецификация правил форматирования. Например, опция точности для чисел с плавающей запятой недоступна для строк.

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

Если есть желание изучить эту тему подробнее — стоит начать с PEP 3101, где описан str.format. Могу, также, порекомендовать презентацию Эрика Смита с достаточно хорошим саммари по этой теме. Есть и хорошие примеры о том как перейти от использования % к str.format в документации python

Надеюсь, вам понравилось!

P… S.: Автор оригинальной статьи замерил производительность str.format и %. Пришел к выводу, что % быстрее

P.P.S.: По словам Андрея Светлова svetlov (словам которого можно доверять в силу его вхожести в команду разработки python) — синтаксис % не будет убран из python 3.x минимум ближайшие 20 лет
А как вы форматируете строки?

Проголосовало 713 человек. Воздержалось 148 человек.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Перевод: Reuven Lerner
@alrusdi
карма
124,2
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (39)

  • +12
    Огромное спасибо! Я даже не слышал о том, что % устарел.
    • 0
      Рад, что Вам пришлось по душе. Начальную версию было не очень приятно читать — много опечаток и стилистических ляпов. Постарался насколько мог подправиь.
    • +11
      % не устарел, более того на текущий момент нет никаких планов по исключению его из 3-й ветки. Несколько лет назад, такие попытки действительно предпринимались, но после дискуссии в сообществе % оставили на месте.

      И еще примеры с str.format не впечатляют, потому что:

      "Good morning, %(first)s %(last)s" % {'first':'Reuven', 'last':'Lerner'}
      • +3
        и даже

        "Good morning, %(first)s %(last)s" % vars()
        
  • 0
    А что на счет скорости? Слышал, что format несколько медленне, чем %. Не могу найти ссылку, где были примеры сравнения скорости.
    • –1
      Полагаю, за большую функциональность всегда приходится чем-нибудь заплатить. Если найдете хорошо аргументированную статью со сравнением производительности это будет отличным дополнением к статье.
      Но вопрос: что быстрее работает — то, что есть в языке или то чего нет по-моему ответа не требует)
      • +1
        И оператор % и метод format — встроенные в язык средства.
        • 0
          Смысл моей фразы состоял в том, что если синтаксис % уберут из языка, то сравнивать его производительность со str.format потеряет смысл. Но похоже, что никто его никуда не уберет, поэтому Вы все-таки правы.
    • +4
      >Слышал, что format несколько медленне, чем %.

      Это даже не на спичках экономия.
      • +3
        Если это выполняется внутри большого цикла, то пусть и не фуру, но небольшой прицеп со спичками можно сэкономить.
        • +5
          Кстати, банальная замена %s на %d для целочисленного типа сокращает время выполнения в несколько раз. Сам проверял через timeit.
    • 0
      Что вам мешает проверить самому? Читайте про timeit
    • 0
      Он медленнее, да: blog.lerner.co.il/relative-speeds-str-format/
  • 0
    Вот что неприятно с str.format — это если пишешь на Python кодогенератор для Си, то фигурные скобки приходится дважды писать:

    >>> "int {varname}[] = {{a, b, c, d}}".format(varname="test")
    'int test[] = {a, b, c, d}'
    >>> "int %(varname)s[] = {a, b, c, d}" % dict(varname="test")
    'int test[] = {a, b, c, d}'
    


    Было бы классно, если бы для str.format можно было вместо фигурных скобок задавать свой разделитель. Например, < и >
    • +4
      Кодогенератор с помощью форматирования строк не выглядит хорошей идеей. Или это просто наколенный инструмент?
      • +1
        Там не столько код, сколько всякие константы, в том числе массивы и структуры. Использовать монструозный «правильный» инструмент смысла особого нет.
        • +4
          Попробуй какой-нибудь простой движок шаблонов, например Mako или Jinja2.
  • 0
    Больше всего в некоторых случаях раздражает этот самый «микроязык» str.format, а именно, как вы и указали, отсутствие slice, вызова методов, ну и подобных мелочей вроде тернарного оператора или простой арифметики. Было бы ещё что-нибудь вроде unsafe format, чтобы можно было использовать любые питоновские выражения…
    • +5
      Зачем выносить логику из языка в строки? Тернарный оператор и арифметика доступны в самом питоне. А если вам нужны продвинутые средства форматирования (например, в веб-приложениях или в программах генерации текстовых данных) — есть куча template библиотек.
    • +1
      Ради вызова методов можно «скостылить» что-то вроде:

      >>> class Wrap(object):
      ...     def __init__(self, inner):
      ...         self.inner = inner
      ...     def __getattr__(self, attr):
      ...         if '(' in attr:
      ...             return eval('this.' + attr, {}, {'this': self.inner})
      ...         return getattr(self.inner, attr)
      ... 
      >>> "{d.get('b', 'default')}".format(d=Wrap({'a': 42}))
      'default'
      

      eval как раз и добавит чуточку «unsafe» ;)

      З.Ы. это, скорее, упражнение на смекалку. На практике такого делать, разумеется, не стоит :)
  • –4
    А всё потому что отсутствует интерполяция строк, вот и мучаемся.
  • +11
    Года полтора назад просматривая стандартную библиотеку Python 3.2 я заметил, что во многих случаях форматирование делается с помощью оператора %. Это немного насторожило, всё-таки стандартная библиотека. И вот что интересно, в документации к Python 3.2. говорится, что
    Since str.format() is quite new, a lot of Python code still uses the % operator. However, because this old style of formatting will eventually be removed from the language, str.format() should generally be used.
    Однако, документация Python 3.4 выглядит немного по-другому:
    The % operator can also be used for string formatting. It interprets the left argument much like a sprintf()-style format string to be applied to the right argument, and returns the string resulting from this formatting operation.

    Более того, в новой документации есть параграф Python 3.4 4.7.2 printf-style String Formatting. Для себя я определился так: использую оба метода, но % — только для простейших случаев, вроде "...%s..." % var.
  • +4
    Есть одна серьёзная причина, почему до удаления %-интерполяции далеко.

    Модуль logging.

    К сожалению, выдача сообщений в нём целиком завязана на %-интерполяцию, и поправить «все печати в логи везде» будет непросто.
  • +6
    % никогда не уйдет из Python 3, если что.
    Это не рекомендованный способ, но им можно пользоваться без опасений что разработчики его когда-то уберут (не в ближайшие двадцать лет — точно).
    • 0
      Почему тогда запилили str.fomat, если вы близки к этому вопросу (по крайне мере рассылку наверняка читаете)? Вроде как дзену противоречит…
      • +2
        Потому что % не очень хорош, str.format лучше.
        Это если коротко.
        • 0
          По мне так он не настолько лучше, чтобы вводить новый, никому не известный стандарт. Вот если бы у этого format-а был функционал жинжи, было бы действительно здорово :)
          • +1
            Большинству людей не нужны продвинутые средства форматирования, функционал жинжи скорее бы всего привёл к замедлению работы библиотеки в целом, что для большинства людей не имеет смысла. Когда мне надо продвинутое форматирование строк, я просто беру жинжу и форматирую :)
            • +1
              Ну, я о том и говорю. Функционал остался тем же. А знакомый адептам других языков формат заменили каким-то велосипедом :)
              • 0
                Я отвечал на вторую часть твоего сообщения «Вот если бы… был функционал жинжи, было бы… здорово» :) Или ты имел в виду % как раньше, а .format для жинжи-фокусов?
                • 0
                  Ага
                • 0
                  Сейчас, независимо от того, какой формат ты предпочитаешь нужно знать оба, чтобы понимать чужой код. Новичкам это наверное совсем странным кажется. Раз уж запилили новый, хоть бы старый выпилили что ли :)
                  • +1
                    Да. Это философию питона нарушает «There should be one-- and preferably only one --obvious way to do it.»
  • 0
    Собачку жалко…
  • –4
    Мне с первого взгляда не понравился %, пишу u"".format() на автомате.
  • +8
    Из плюсов .format() — в нем можно добавлять свои модификаторы формата для собственных типов данных.

    К примеру,
    >>> class C(object):
    	foo = 1
    	def __format__(self, spec):
    		if spec == 'yes':
    			return 'format done'
    		else:
    			return str(self.foo)
    
    		
    >>> c = C()
    >>> "{}, {:yes}".format(c, c)
    '1, format done'
    
    
    


    сам раньше использовал только %, сейчас перехожу на .format() — для меня тут больше гибкости.
    • +1
      Это, по-моему, главный и единственный его плюс.
  • 0
    Нет нечего лучше дрессировки )
  • +2
    Начинал читать топик с мыслью «ну вот, сейчас Капитан Очевидность расскажет нам о возможности вывести одну переменную несколько раз», а тут такое! :)

    Спасибо, отличная статья и хорошее качество перевода.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.