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

f577df678f50473783bf17dbe79edd0d.jpgПредлагаю вниманию читателей хабры и поклонникам 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» и «lost». По-старому мы сделали бы так:

>>> 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 ' С помощью квадратных скобок можно взять элемент итерируемого объекта (списка, строки) по индексу. Но операции разрезания (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

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

© Habrahabr.ru