Вывод данных в консоль в Lua: от print до io.write
Опубликовано:
- В Lua для вывода в консоль чаще используется функция
print. - Под капотом она вызывает
tostringдля всех типов данных. - Метаметод
__tostringпереопределяет поведениеtostring. Пример кастомизации вывода таблицы. - Почему в Lua конкатенация строк выполняется через двойную точку (
..). - Функция
printпри выводе разделяет аргументы табуляцией, а не пробелом. - Форматирование вывода осуществляется с помощью функции
string.format. - Функция
printвсегда добавляет переход на новую строку. - Для тонкой настройки вывода на экран, его динамического изменения используется функция
io.write. - В паре с
io.writeнередко вызываетсяio.flushдля выталкивания символов из буфера памяти. - Особенности
io.write: отсутствие приведения типов, аналог методаfile:writeдля вывода в файл.
В Lua в большинстве случаев вывод на экран выполняется с помощью функции print.
local f = -0.258 local s = "Hello" print(f) -- -0.258 print(s) -- Hello print(102 + 39) -- 141 print(false) -- false print(nil) -- nil
При этом print принудительно вызывает для своих аргументов функцию tostring, преобразующую данные к строчному типу. В случае когда tostring получает уже строку, она просто возвращает ссылку на нее же саму.
local a = 10 local b = tostring(a) -- Если надо сохранить данные в виде строки print(a) -- Преобразование было print(b) -- Преобразования не было
Функция tostring возвращает таблицы в виде строк типа "table: 0x...", в которых указан адрес объекта в памяти. Чтобы вывод таблицы кастомизировать (настроить по своему), в ее метатаблицу добавляется метаметод __tostring. Он будет срабатывать при передаче таблицы в tostring.
local user = { name = "Luka", age = 35 } print(user) -- table: 0x5754284effd0 setmetatable(user, { __tostring = function(self) return self.name.." "..self.age end }) print(user) -- Luka 35
В Lua все метаметоды должны находиться именно в метатаблице-классе. Если же добавить __tostring в саму таблицу-объект, он не будет работать.
Из примера выше мы видим еще одну особенность Lua. Конкатенация строк здесь выполняется через две точки, а не знак плюса, как во многих других языках программирования. Такой способ выбран не просто так, он позволяет применять разную логику. Если в "объединяющем" выражении стоят число и строка, то в случае плюса строка будет преобразовываться к числу, в случае двойной точки — число станет строкой.
print("10" + 35) -- 45 (строка "10" стала числом 10) print("10" .. 35) -- 1035 (число 35 стало строкой "35") print(10 .. 35) -- 1035 print("10" + "35") -- 45
Стандартная функция print в Lua может принимать несколько аргументов через запятую. При этом в выводе она разделяет их знаками табуляции (\t), а не пробелами, как это было бы в Python.
print('One', 1, 'I', 1.0) print('One', '', 1, 'I', 1.0) print('Seventeen', 17, 'XVII', 17.0)
user@home:~$ lua example.lua One 1 I 1.0 One 1 I 1.0 Seventeen 17 XVII 17.0
Поскольку терминал выравнивает табуляцию по фиксированным колонкам, пробельное расстояние между выведенными значениями может оказаться разным. Также расстояние зависит от настроек самого терминала (табуляция может быть в 4 или 8 пробелов).
Использование автоматического табулятора связано с тем, что функция print в Lua создавалась в первую очередь как инструмент для быстрой отладки и вывода простых отчетов. Табуляция позволяет выравнивать данные в ровные колонки (таблицы) при выводе в консоль, что удобно для чтения логов.
Вместо строковых шаблонов, которых в Lua нет, форматирование вывода и того, как выглядят данные (например, вывод числа с плавающей точкой до двух знаков после запятой), осуществляется аналогично printf в языке Си. Но в Lua для этого дополнительно используется функция string.format.
local pi = 3.14159 print(string.format("%s %s", "one", "two")) print(string.format("Пи: %.2f", pi)) print(string.format("%d %5d %05d", 45, 86, 13)) print(string.format("%-d %-5d %-05d", 45, 86, 13)) local hello = string.format("Hello, %s!", "Tom") print(hello)
one two Пи: 3.14 45 86 00013 45 86 13 Hello, Tom!
Функция string.format находится в модуле string. При этом в Lua из-за специфики работы со строками её можно вызывать двумя разными способами: как функцию модуля, когда строка шаблона передается первым аргументом, и как метод самой строки. В Lua у всех строк есть общая метатаблица, где поле __index указывает на модуль string. Это значит, что любая строка автоматически обладает всеми функциями этой библиотеки.
local h = ("Hello, %s!"):format("Tom") print(h) -- Hello Tom!
Под капотом оба способа идентичны, так как во втором случае Lua сам подставит строку в качестве первого аргумента.
Функция print всегда добавляет перенос строки (\n). Простой возможности не делать этого, как в Python через параметр end, в Lua нет. Поэтому когда нужно вывести текст без такого перехода или динамически изменять символы в той же строке терминала используют более низкоуровневую функцию io.write. Например, в текстовых интерфейсах часто нужно запрашивать данные у пользователя в той строке, где написан вопрос:
io.write('First Name: ') local f_name = io.read() io.write('Last Name: ') local l_name = io.read() io.write(f_name, ' ', l_name, '\n')
user@home:~$ lua example.lua First Name: Robert Last Name: Green Robert Green user@home:~$
Функции print и io.write работают с системным буфером. В операционных системах буферизация вывода, когда текст не выводится в консоль мгновенно по одной букве, позволяет экономить ресурсы. ОС (ее низкоуровневая библиотека ввода-вывода) копит символы в буфере памяти, пока не получит символ перехода на новую строку. Так как io.write не переносит строку, система может "подумать", что вы еще не закончили, и не покажет текст вовремя. Поэтому io.write часто используется в паре с функцией io.flush, которая принудительно выталкивает имеющееся содержимое буфера.
io.write("Загрузка: ") for i = 1, 10 do io.flush() -- принудительно сразу выталкиваем текст на экран local start = os.clock() while os.clock() - start < 0.2 do end -- ожидание 0.2 секунды io.write(".") end print() -- для перехода на новую строку
Результатом выполнения этого кода, независимо есть там команда io.flush() или нет, будет строка Загрузка: ........... Однако, если io.flush() есть, точки будут постепенно появляться через каждые 0.2 секунды, без io.flush() вы увидите всю строку целиком через 2 секунды.
В отличие от print, функция io.write автоматически не вызывает tostring для своих аргументов. Поэтому попытки с ее помощью вывести любые другие типы данных, кроме как строки и числа, будут приводить к ошибкам. Конвертация чисел в строки происходит на "низком" уровне — силами самого Си-движка Lua.
Для записи в файлы используется метод file:write, особенности работы которого аналогичны функции io.write. Это разные объекты (функция в модуле io и метод в метатаблице файла) на уровне Lua, но оба являются обертками к одной и той же функции на уровне Си-движка.