Новые и старые модули
Это перевод вот этой статьи.
Создание и использование модулей
Есть два способа создания модулей — старый(и не рекомендуемый) для 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
.
Остальные поля не очень важны для обычного использования модулей, но если вас интересует, как работает система модулей, это детально описано в мануале здесь.
leave a comment