Я люблю Lua. I love Lua.

Новые и старые модули

Posted in Uncategorized by ilovelua on 23 января, 2014

Это перевод вот этой статьи.

Создание и использование модулей

Есть два способа создания модулей — старый(и не рекомендуемый) для 5.0 и ранних версий 5.1, и новый для 5.1 и 5.2. Кроме нового способа мы также рассмотрим старый, поскольку вам могут встретиться программы, в которых он используется.

Создадим файл примера с следующим содержанием:

local mymodule = {}

function mymodule.foo()
    print("Hello World!")
end

return mymodule

Теперь, чтобы использовать этот модуль в интерактивном интерпретаторе, напишите:

> mymodule = require "mymodule"
> mymodule.foo()
Hello World!

В обычном файле скрипта рекомендуется сделать переменную mymodule локальной:

local mymodule = require "mymodule"
mymodule.foo()

Если мы сделаем переменную mymodule локальной в интерактивном интерпретаторе, то на следующей строке она выйдет из области видимости и мы не сможем ей воспользоваться. Поэтому мы должны сделать переменную mymodule глобальной.

Поскольку вы можете подключать один и тот же модуль в различных файлах, Lua кеширует модули в таблице package.loaded. Чтобы показать, как работает кеширование, изменим, например, функцию foo в mymodule.lua чтобы она печатала "Hello Module!". Если мы продолжим сессию в интерпретаторе, то произойдет следующее:

> mymodule = require "mymodule"
> mymodule.foo()
Hello World!

Чтобы действительно перезагрузить модуль, нужно сначала удалить его из кеша:

> package.loaded.mymodule = nil
> mymodule = require "mymodule"
> mymodule.foo()
Hello Module!

Еще одна приятная вещь — это возможность именовать модули произвольным образом, поскольку модули это обычные таблицы, сохраненные в переменной. Если название mymodule.lua покажется нам слишком длинным, мы можем его сократить:

> m = require "mymodule"
> m.foo()
Hello Module!

Другие способы создания модулей

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

Создать таблицу в начале скрипта и добавлять функции в нее:

local mymodule = {}

local function private()
    print("in private function")
end

function mymodule.foo()
    print("Hello World!")
end

function mymodule.bar()
    private()
    mymodule.foo() -- need to prefix function call with module
end

return mymodule

Сделать все функции локальными и добавить их в таблицу в конце скрипта:

local function private()
    print("in private function")
end

local function foo()
    print("Hello World!")
end

local function bar()
    private()
    foo() -- do not prefix function call with module
end

return {
  foo = foo,
  bar = bar,
}

Комбинация двух предыдущих способов:

local mymodule = {}

local function private()
    print("in private function")
end

local function foo()
    print("Hello World!")
end
mymodule.foo = foo

local function bar()
    private()
    foo()
end
mymodule.bar = bar

return mymodule

Вы даже можете изменить окружение блока кода(the chunk’s environment), чтобы сохранить в нем только необходимые глобальные переменные:

local print = print -- the new env will prevent you from seeing global variables

local M = {}
if setfenv then
	setfenv(1, M) -- for 5.1
else
	_ENV = M -- for 5.2
end

local function private()
    print("in private function")
end

function foo()
    print("Hello World!")
end

function bar()
    private()
    foo()
end

return M

Или, если вы не хотите сохранять глобальные переменные:

local M = {}
do
	local globaltbl = _G
	local newenv = setmetatable({}, {
		__index = function (t, k)
			local v = t[k]
			if v == nil then return globaltbl[k] end
			return v
		end,
		__newindex = M,
	})
	if setfenv then
		setfenv(1, newenv) -- for 5.1
	else
		_ENV = newenv -- for 5.2
	end
end

local function private()
    print("in private function")
end

function foo()
    print("Hello World!")
end

function bar()
    private()
    foo()
end

return M

Обратите внимание, что в этом случае доступ к глобальным переменным и переменным модуля может стать медленнее, поскольку используется функция __index. Пустая «прокси» таблица используется вместо таблицы _G(глобальное пространство имен) для того, чтобы не было возможности из модуля обращаться к глобальным переменным и не происходило следующее:
> require "mymodule"
> mymodule.foo()
Hello World!
> mymodule.print("example") -- unwanted __index metamethod
example

Старый способ создания модулей

В Lua 5.0 and 5.1 есть функция module, которая используется таким образом:
mymodule.lua:

module("mymodule", package.seeall)

function foo() -- create it as if it's a global function
    print("Hello World!")
end

И модуль может использоваться так:

> require "mymodule"
> mymodule.foo()
Hello World!

Работает это так: создается новая таблица для модуля, сохраняется в глобальной переменной, имя которой задается первым параметром функции module, затем эта таблица устанавливается окружением для кода модуля, так что если вы создаете в модуле глобальную переменную, то она сохраняется в таблице модуля.

Из-за этого вы внутри модуля не видите глобальных переменных(например, print). Одно из решений — сохранить все необходимые глобальные переменные в локальных перед вызовом функции module, но это может быть утомительно, и решением было добавить второй параметр в функцию module, который должен быть функцией, в которую таблица модуля передается как параметр. package.seeall устанавливает для модуля метатаблицу с функцией __index, которая указывает на глобальное пространство имен, и благодаря этому модуль может обращаться к глобальным переменным. Проблема этого решения в том, что пользователь модуля может обращаться к глобальным переменным через модуль:

> require "mymodule"
> mymodule.foo()
Hello World!
> mymodule.print("example")
example

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

Таблица package

Как было сказано ранее, Lua использует библиотеку package для управления модулями.

package.path (для модулей написанных на Lua) и package.cpath (для модулей написанных на C) это места, где Lua ищет модули.

> =package.path
./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua;/usr/share/lua/5.1/?.lua;/usr/share/lua/5.1/?/init.lua
> =package.cpath
./?.so;/usr/local/lib/lua/5.1/?.so;/usr/lib/x86_64-linux-gnu/lua/5.1/?.so;/usr/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so

package.loaded это таблица, где уже загруженные модули хранятся по именам. Как обсуждалось ранее, это работает как кеш, чтобы модули не загружались многократно, функция require сначала ищет модуль в этой таблице, и, если ничего не найдено, пытается его загрузить.

package.preload это таблица функций, связанных с именами модулей. Прежде чем искать в файловой системе, require проверяет, не содержит ли package.preload нужный ключ. Если функция найдена, то результат ее вызова и будет значением, которое возвратит require.

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