1. ホーム
  2. スクリプト・コラム
  3. ルア

Luaチュートリアル(10)。グローバル変数と非グローバル環境

2022-02-13 21:44:56

Luaは、すべてのグローバル変数を"environment"と呼ばれる通常のテーブルで管理しています。これは、グローバル変数_Gに格納されています。

1. グローバル変数の宣言。

Luaのグローバル変数は、宣言しなくても使用することができます。便利ではありますが、ペンシルエラーが発生した場合、発見しにくいエラーの原因となります。Gテーブルにメタテーブルを追加することで、グローバル変数の読み書きを保護することができ、このようなタイプミス問題が発生する可能性を低くすることができます。次のコード例をご覧ください。

コピーコード コードは以下の通りです。

-- This table is used to store the names of all the global variables that have been declared
local declaredNames = {}
local mt = {
    __newindex = function(table,name,value)
        -- First check if the new name has already been declared, and if it exists, this is set directly via the rawset function.
        if not declaredNames[name] then
            -- Check again if this operation was done in the main program or C code, and if so, continue to set it, otherwise report an error.
            local w = debug.getinfo(2,"S").what
            if w ~= "main" and w ~= "C" then
                error("attempt to write to undeclared variable " . name)
            end
            -- Update the declaredNames table before you actually set it, so you don't have to check it the next time you set it.
            declaredNames[name] = true
        end
        print("Setting " . name ... " to " ... value)
        rawset(table,name,value)
    end,
    __index = function(_,name)
        if not declaredNames[name] then
            error("attempt to read undeclared variable " . name)
        else
            return rawget(_,name)
        end
    end
}   
setmetatable(_G,mt)

a = 11
local kk = aa

-- The output is.
--[[
Setting a to 11
lua: d:/test.lua:21: attempt to read undeclared variable aa
stack traceback:
        [C]: in function 'error'
        d:/test.lua:21: in function <d:/test.lua:19>
        d:/test.lua:30: in main chunk
        [C]: ?
--]]

 2. 非グローバルな状況。

グローバル環境は、その変更がプログラムのすべての部分に影響するという硬直的な問題を抱えています。Lua 5ではこの点が改善され、各関数がそれぞれ独立したグローバル環境を持ち、その関数によって生成されたクロージャ関数がその関数のグローバル変数テーブルを継承するという新機能がサポートされています。ここで、関数の環境を変更するには、関数名と新しい環境テーブルの2つの引数を取るsetfenv関数を使います。第1引数には、関数名自体の他に、現在の関数呼び出しスタックのレベル数を示す数値を指定することが可能です。1は現在の関数、2はその呼び出し関数を示し、以下同様である。次のコードを見てほしい。

コピーコード コードは以下の通りです。

a = 1
setfenv(1,{})
print(a)

-- The output is.
--[[
lua: d:/test.lua:3: attempt to call global 'print' (a nil value)
stack traceback:
        d:/test.lua:3: in main chunk
        [C]: ?
--]]

なぜこのような結果になるのでしょうか?なぜなら、printは変数aと同様にグローバルテーブルのフィールドであり、新しいグローバルテーブルは空なので、printの呼び出しはエラーを報告します。

この副作用に対抗するには、古いグローバル・テーブル _G を新しいグローバル・テーブルの内部テーブルとして機能させ、既存のグローバル変数にアクセスするときは _G のフィールドに直接行き、新しいグローバル・フィールドについては新しいグローバル・テーブルで保持すればよいのです。こうすれば、関数内で間違った変更があっても、そのグローバル変数(_G)が使われている他の場所に影響を与えることはないでしょう。次のコードを見てください。

コピーコード コードは以下の通りです。

a = 1
local newgt = {} -- New environment table
setmetatable(newgt,{__index = _G})
setfenv(1,newgt)
print(a) -- output 1

a = 10
print(a) -- output 10
print(_G.a) -- output 1
_G.a = 20
print(a) -- output 10

最後に挙げたのは、関数の環境変数の継承の例です。次のコードを見てください。

コピーコード コードは以下の通りです。

function factory()
    return function() return a end
end
a = 3
f1 = factory()
f2 = factory()
print(f1()) - output 3
print(f2()) --output 3

setfenv(f1,{a = 10})
print(f1()) -- output 10
print(f2()) -- output 3