Шаг 37.
Python: сборник рецептов.
Строки и текст. Интерполяция переменных в строках

    На этом шаге мы рассмотрим различные способы решения этой задачи.

Задача

    Вы хотите создать строку, в которой на место переменных будут подставляться строковые представления значений этих переменных.

Решение

    В Python нет прямой поддержки простой подстановки значений переменных в строках. Однако строковый метод format() предоставляет приближенную по смыслу возможность. Например:

>>> s = '{name} has {n} messages.'
>>> s.format(name='Guido', n=37)
'Guido has 37 messages.'
>>> 

    Если значения, которые должны быть подставлены, на самом деле находятся в переменных, вы можете использовать сочетание format_map() и vars(), как показано тут:

>>> name = 'Guido'
>>> n = 37
>>> s.format_map(vars())
'Guido has 37 messages.'
>>>

    Стоит отметить, что vars() также работает с экземплярами. Например:

>>> class Info:
	def __init__(self, name, n):
		self.name = name
		self.n = n

		
>>> a = Info('Guido', 37)
>>> s.format_map(vars(a))
'Guido has 37 messages.'
>>> 

    Недостаток format() и format_map() в том, что они не могут аккуратно справиться с отсутствующими значениями. Например:

>>> s.format(name='Guido')
Traceback (most recent call last):
 .   .   .   .
KeyError: 'n'
>>>

    Этого можно избежать путем определения альтернативного класса словаря с методом __missing__(), как показано ниже:

class safesub(dict):
	def __missing__(self, key):
		return '{' + key + '}'

	
>>> 

    Теперь этот класс можно использовать, чтобы обернуть значения, которые подаются на вход в format_map():

>>> del n # Убедимся, что n не определена
>>> s.format_map(safesub(vars()))
'Guido has {n} messages.'
>>> 

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

>>> import sys
>>> def sub(text):
	return text.format_map(safesub(sys._getframe(1).f_locals))

>>> 

    Теперь вы можете делать вот так:

>>> name = 'Guido'
>>> n = 37
>>> print(sub('Hello {name}'))
Hello Guido
>>> print(sub('You have {n} messages.'))
You have 37 messages.
>>> print(sub('Your favorite color is {color}'))
Your favorite color is {color}
>>> 


Обсуждение

    Отсутствие настоящей интерполяции переменных в Python привело к созданию разнообразных решений. В качестве альтернативы описанным выше вы иногда можете увидеть такой подход к форматированию строк:

>>> name = 'Guido'
>>> n = 37
>>> '%(name) has %(n) messages.' % vars()
'Guido has 37 messages.'
>>>

    Или же вам могут попасться шаблонные строки:

>>> import string
>>> s = string.Template('$name has $n messages.')
>>> s.substitute(vars())
'Guido has 37 messages.'
>>> 

    Однако методы format() и format_map() являются более современными, нежели эти альтернативы, и отдавать предпочтение нужно им. Преимущество использования format() заключается в том, что вы также получаете все возможности форматирования строк (выравнивание, отступы, нумерацию и т. п.), что недоступно для альтернативных решений, таких как строковые объекты Template.

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

    Функция sub() использует sys._getframe(1), чтобы вернуть фрейм стека вызовов. Отсюда атрибут f_locals доступен, чтобы получить локальные переменные. Стоит отметить, что заигрывания с фреймами стека стоит избегать. Однако для вспомогательных функций типа строковой подстановки это может оказаться полезным. Отдельно заметим, что f_locals - это словарь, который является копией локальных переменных в вызывающей функции. Хотя вы можете изменить содержимое f_locals, эти изменения не станут постоянными. Поэтому, хотя доступ другому фрейму стека и может показаться адским злом, невозможно случайно переписать переменные или изменить локальное окружение вызывающей функции.

    На следующем шаге мы рассмотрим разбивку текста на фиксированное количество колонок.




Предыдущий шаг Содержание