December 7, 2020

Форматированный вывод

Подавляющее большинство мануалов по тем или иным языкам программирования показывают прекрасный пример того, как начинающему программисту написать свой первый "Hello, world!" и это прекрасно! Давайте разбираться, почему этого недостаточно и как сделать жизнь программиста чуть лучше. Да, по форматированному выводу написано также огромное количество мануалов, в том числе в стандартных документациях к языкам, но почему бы не сделать попытку отказаться от казённого языка больших компаний и не рассказать своими словами?

Немного истории

Современные языки программирования, так или иначе, но берут своё начало в C.

...

...

...

Так, мы подождали, когда нас покинут эксперты в области истории программирования и можем спокойно продолжать. Подавляющее большинство так называемых "языков высокого уровня" содержат отличную стандартную библиотеку функций (классов и методов), в которой описана функция print() она обычно либо лежит в системном классе, либо в другом очень удобном и доступном месте. Все люди ей радостно пользуются, и если нужно, например, собрать строчку из нескольких составляющих просто применяют конкатенацию строк, получая что-то вроде print("Hello, I'm " + "Ivan " + 35 + " years of age");. Всё очень хорошо и понятно. но можно лучше. Разработчики языков программирования придумали, как избавиться от конкатенации и удобно форматировать строки. Нужно написать параметризированный шаблон и подставлять в него значения. Всего навсего.

Понятие форматирования

Всё началось в K&R C, где функция print(); не была описана вовсе. Это было для меня камнем преткновения изучения C в школе, где на уроках информатики нам преподавали QBasic. Я использовал директиву print(); а компилятор упорно утверждал, что не знает такой. Удивительно, что простой вывод осуществлялся функцией printf(); что расшифровывалось как print formatted. Форматированный вывод подразумевал как раз создание шаблона строки и заполнения его параметрами, хотя и простой "Hello, world" с его помощью вывести можно. Что представляет собой форматирование? Это отличается от того, к чему мы привыкли при форматировании текста в HTML или MS Word, где под этим словом подразумевается создание отступов, интервалов и прочих украшательств. В программировании форматирование - это создание таких мест в строке, которые будут заполнены значениями, так или иначе переданными шаблону. Места, которые будут заполнены значениями отмечаются в шаблоне специальной последовательностью, которая называется placeholder (англ. - держатель места, заполнитель). То есть, если мы опишем вызов шаблона "Hello, %s" и передадим в него литерал "world" вся строка в шаблоне выведется "как есть", а заполнитель заменится на переданный литерал. Заполнитель представляет собой символ % и некоторую последовательность, означающую, собственно, формат принимаемого в заполнитель значения. Самые популярные заполнители - это:
- %s - string, обычный строковый литерал. Представляется "как есть", не дополняется никакими символами и не обрезается.
- %d, %i - decimal, integer - целое число в десятичной системе счисления. Разницы в использовании %d и %i нет
- %x, %X - hexadecimal - целое число в шестнадцатиричном представлении. Разница между %x и %X в том, что в итоговой строке будут использованы строчные или заглавные буквы A-F для представления чисел 10-15 - %f - float - числа с плавающей точкой (дробные числа)
- %c - character - символ.
- %% - собственно, символ процента. Поскольку с него начинается специальная последовательность, чтобы вывести символ "как есть" нужно было положить его внутрь последовательности. Выкрутились.

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

Некоторые заполнители могут дополняться параметрами, так, например, %5d будет означать, что переданное целое число будет помещено в пространство из не менее, чем пяти символов (слева будут дописаны пробелы, если число занимает меньше символов). Используя же %05d мы будем минимальное выделенное пространство заполнять не пробелами, а нулями. Или, к примеру, в случае %.2f мы указываем, что дробную часть числа при отображении следует обрезать до двух символов, и число 1.23456 будет отображено как 1.23.

C/C++


Функция printf(); это весьма удобная альтернатива простому выводу в поток вывода в C++. Оператор std::cout не предоставляет возможности к форматированию строк и поэтому, несмотря на свою популярность, не может считаться равноценной заменой классическому printf(); Более того в языке присутствует функция sprintf(); позволяющая осуществлять форматированный вывод не в консоль, но в другой строковый литерал.

char fmt[20];
sprintf(fmt, "shift |%%%dd|, 7);
// no output, but fmt == "shift |%7d|"
printf(fmt, 10);
printf("preformatted with zeros %07d", 10);
// shift |     10|
// preformatted with zeros 0000010

Java


В Java классические форматтеры представлены методами System.out.printf(); (а если быть совсем точным, то PrintStream#printf();) и String#format(); первый осуществляет вывод форматированной строки в поток, второй в строку.

System.out.printf("%s loves string formatting\n", "Everyone");
System.out.println(String.format("Sadly, %.2f%% of programmers use it", 50f));
// Everyone loves string formatting
// Sadly, 50.00% of programmers use it

Python


Здесь форматирование осуществляется всегда для строки и может использоваться внутри функции print(), например, для заполнения значением конкретной переменной

customstring = "String formatting"
print(f"{customstring} is powerful")
# String formatting is powerful

или близким к классическому способом

print("Machine learning provides {} the ability to learn {}".format("systems", "automatically"))
# Machine learning provides systems the ability to learn automatically

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

print("{2} has a friend called {0} and a sister called {1}". format("Betty", "Linda", "Daisy"))
# Daisy has a friend called Betty and a sister called Linda

tool="Unsupervised algorithms"
goal="patterns"
print("{title} try to find {aim} in the dataset".format(title=tool, aim=goal))
# Unsupervised algorithms try to find patterns in the dataset

Выводы

Естественно, каждый может найти как десяток доводов продолжать использовать форматирование строк, так и десяток доводов продолжать их не использовать. В этой статье я постарался ответить на вопрос "а что это у Вас за странный вызов на печать такой", а не дать читателю завет "всегда пользуйся только вот этим". Поиск доводов "за" и "против" автор всегда оставляет читателю.