lua入門ノート4環境モジュールとパッケージ
9479 ワード
1環境
我々がluaで使用するすべてのグローバル変数は、環境(environment)と呼ばれる従来の 1. 動的な名前を持つグローバル変数 グローバル変数にアクセスしたり設定したりする必要がある場合、メタプログラミングの形式も使用されます.例えば次の操作
実際には次の方法で直接アクセスできます 2. グローバル変数宣言 グローバル変数は通常の
このコードは、すべてのグローバル 3. 非大域的な環境 以前の環境のもう一つの大きな問題は彼が全局的であることにある.ローカル変更後、グローバルは有効になります.たとえば、グローバルメタテーブルを上の制御グローバル変数に設定してアクセスを宣言すると、プログラムにこの仕様を守らない場所があると、プログラムにエラーが発生します.
環境が変更されると、すべてのグローバルアクセスに新しいtableが使用されます.新しいtableが空の場合、
このコードでは、新しい環境は元の環境から
ここでfactoryは、factoryを呼び出すたびに新しい閉パケットとその閉パケットに属する環境を創出する、彼のグローバルaの値を返すための単純な
2.モジュールとパッケージ
モジュールシステムの主な目的は、異なる形式でコードを共有することを可能にすることである.しかし、このような共有は、共通のルールがなければ実現できません.
または
関数に別名を指定することもできます
1.require関数
まず、ロードされたかどうかを確認し、ロードされた場合は直接ロードに成功したことを返します.存在するかどうかを検索していない場合は、戻ります.
2.モジュール作成の基本方法
上記の例では、
3.使用環境
関数環境は興味深い技術であり,モジュールのメインブロックに独占的な環境を持たせることが基本的な考え方である.これにより、そのすべての関数がこの
このとき,関数
刺種法はモジュール内に局所変数でグローバルへの参照を保存することに相当し,使用する場合は
この方法は,環境を変える前に,すべての内部で必要とされる外部変数を事前に引用し,以前の2つよりも正規である.
4.module関数
前の例では,先頭のコードが似ていることが分かった.
我々がluaで使用するすべてのグローバル変数は、環境(environment)と呼ばれる従来の
table
に保存されている.彼はtable
なので、他のtable
を操作するように操作することができます.このような動作を実施するために、table
は、環境lua
自身をグローバル変数table
に保存する.たとえば、for n in pairs(_G) do
ptinr(n)
end
value=loadstring("return "..varname)() --varname:
実際には次の方法で直接アクセスできます
_G[varname]=value
_G
にのみ格納されるため、メタテーブルを使用してアクセスの動作を変更できることを意味します.例えばsetmetatable(_G,{
__newindex=function(_,n)
error("attempt to write to undeclared value "..n,2)
end,
__index=function(_,n)
error("attempt to read a undeclared value "..n,2)
end,
})
このコードは、すべてのグローバル
table
に存在しないtable
へのアクセスにエラーを引き起こしますが、これでは新しい変数を宣言することはできません.したがって、宣言のプロセスは、メタテーブルを迂回する必要があります.例えば、前のkey
およびrawset
rawget
でこの問題を改善し、各関数にグローバル変数を検索するための独自の環境を持つことを許可した.このメカニズムは、後で具体的に説明します.彼らのメリットは、グローバル変数をどこにでもアクセスできることです.Lua5
によって関数の環境を変えることができます.最初のパラメータは、関数自体として指定できるほか、数値で置き換えられています.たとえば、1は現在の関数、2はこの関数を呼び出す関数などです.最初に試してみたときに発生する可能性のあるエラーa=1 --
setfenv(1,{}) -- table
print(a) -- attempt to call global 'print'(a nil value)print table
環境が変更されると、すべてのグローバルアクセスに新しいtableが使用されます.新しいtableが空の場合、
setfenv
などのグローバル変数を含むすべてのグローバル変数が失われます.a=1
setfenv(g=_G)
g.print(a) --nil
g.print(g.a) -- 1
_G
の代わりに_G
を使用してもよい.a=1
setfenv({1,{_G=_G})
_G.print(a) --nil
_G.print(g.a) -- 1
g
にとって、Lua
は普通の名前です._G
が最初のグローバルlua
を作成するとき、このtable
は全集合変数table
に与えられるだけであり、_G
はこのグローバル変数lua
の現在の値を気にしない._G
は、この変数を新しい環境で設定しません.しかし、この新しい環境が最初のグローバルsetfenv
を参照することを望む場合は、一般的にtable
という名前を使用すればよい.もう1つの新しい環境を組み立てる方法は継承ですa=1
local newgt={} --
setmetatable(newgt,{__index=_G})
setfenv(1,newgt) --
print(a)
このコードでは、新しい環境は元の環境から
_G
とprint
を継承していますが、任意の付与は新しいa
で発生します.次に、closureを設計する例について説明します.function factory()
return function()
return a -- a
end
end
a=3
f1=factory()
f2=factory()
print(f1()) -->3
print(f2()) -->3
setfenv(f1,{a=10})
print(f1()) -->10
print(f2()) -->3
ここでfactoryは、factoryを呼び出すたびに新しい閉パケットとその閉パケットに属する環境を創出する、彼のグローバルaの値を返すための単純な
table
を作成する.新しく作成された各閉パケットは、その閉パケットを作成する関数環境を継承します.関数は、その関数を作成する環境を継承します.したがって、ブロック人が独自の環境を変更すると、その後に作成された関数もこの新しい環境を共有し、このメカニズムはネーミングスペースの作成に役立ちます.2.モジュールとパッケージ
モジュールシステムの主な目的は、異なる形式でコードを共有することを可能にすることである.しかし、このような共有は、共通のルールがなければ実現できません.
closure
から始まり、モジュールとパケットの一連のルールが定義される.これらのルールは言語に追加のスキルを導入する必要はなく、closure
、関数、メタテーブルを通じてこれらのルールを実現します.このうち2つの重要な関数は、これらの規則によってそれぞれlua5.1
およびtable
であることが容易である.ユーザの観点から、1つのモジュールは1つのライブラリであり、require( )
によってロードすることができる.次いで、module( )
を表すグローバル変数が得られた.このtableはネーミングスペースであり,その内容はモジュールから導出されたすべてのもの(関数,定数)である.仕様のモジュールはまた、require
を使用して、このtable
に戻る必要があります.例えば、モジュール内の関数を呼び出す必要があります.require "mod"
mod.foo()
または
local m=require "mod"
m.foo()
関数に別名を指定することもできます
require mod
local f=mod.foo()
f()
1.require関数
require
の場合、1つのモジュールは、いくつかの値を定義するコードである.モジュールをロードする必要がある場合、単純にtable
を呼び出すだけで、モジュール関数からなるrequire
が返され、require ""
を含むグローバル変数も定義される.いくつかの使用済みの木がロードされている可能性があることを知っていても、table
を使用すれば良いプログラミング習慣です.ただし、table
が事前にロードされるため、標準ライブラリは除外できます.標準ライブラリのモジュールに表示されているrequire
を使用することもできますlocal m=require "io"
m.write("hello world
")
lua
の動作を以下に詳細に説明するfunction require(name)
if not package.loaded[name] then --
local loader =findloader(name)
if loader==nil then
error("unable to load module "..name)
end
package.loaded[name]=true
local res=loader(name)
if res ~= nil then
package.loaded[name] = res
end
end
return package.loaded[name]
end
まず、ロードされたかどうかを確認し、ロードされた場合は直接ロードに成功したことを返します.存在するかどうかを検索していない場合は、戻ります.
require
は重複ロードを行わない.1つのモジュールがロードされている限り、後続のrequire
は豆乳を呼び出して同じ値を返す.モジュールがロードされていない場合、require
は、モジュールにローダ(loader)を見つけ、require
で入力されたモジュール名がクエリされます.関数が1つ見つかった場合は、その関数をモジュールのローダとして使用します.require
により、様々な異なる状況を処理するための汎用的な方法が得られる.table package.prelado
がpreload table
ファイルを見つけた場合、彼はrequire
を通じてファイルをロードします.一方、Cライブラリが見つかった場合は、lua
でロードされます.どちらの関数もコードをロードしただけで、実行されません.コードを実行するために、loadfile
は、これらのコードをモジュール名をパラメータとして呼び出す.ローダに戻り値がある場合、loadlib
九江という戻り値はrequire
に格納され、戻り値がない場合、require
はtable package.loaded
の値に戻ります.ここでは、ローダを呼び出す前に、require
を先に呼び出し、AがBをロードし、BがAをロードするという無限のループを回避する詳細がある.1つのライブラリに2回強制的にtable package.loaded
をロードする場合は、package.loaded[name]=true
のモジュールエントリを簡単に削除できます.例えば、require
に成功した後、package.loaded
はnilを問わない.次のコードは、モジュールを再ロードできます.package.loaded["foo"]=nil
require "foo"
require "foo"
package["foo"]
ファイルを検索するためのパスは、変数require
に格納される.lua
が起動すると、この変数は環境変数package.path
の値で初期化する.2.モジュール作成の基本方法
lua
でモジュールを作成する最も簡単な方法は、LUA_PATH
を作成し、エクスポートする必要があるすべての関数を挿入し、最後にこのlua
に戻ることです.complex={}
function complex.new(r,i) return {r=r,i=i} end
complex.i=complex.new(0,1)
function complex.add(c1,c2)
return complex.new(c1.r+c2.r,c1.i+c2.i)
end
function complex.sub(c1,c2)
return complex.new(c1.r-c2.r,c1.i-c2.i)
end
function complex.mul(c1,c2)
return complex.new(c1.r*c2.r-c1.i*c2.i,c1.r*c2.i+c1.i*c2.r)
end
local function inv(c) -- ,
local n=c.r^2+c.i^2
return complex.new(c.r/n,-c.i/n)
end
function complex.div(c1,c2)
return complex.mul(c1,inv(c2))
end
return complex
上記の例では、
table
を用いてモジュールを記述する際に、真のモジュールと完全に一致する機能性は提供されず、まず、各関数定義にモジュール名を現実的に配置しなければならない.次に、同じモジュールを呼び出す関数のもう1つの関数は、呼び出される関数の名前を限定する必要があります.モジュールクラスの関数を定義および呼び出すために、固定されたローカル名(例えば、table
)を使用し、モジュールの最終名にこのローカル名を付与することができる.このようにして、前例をlocal M={}
complex=M
M.i={r=0,i=1}
function M.new(r,i) return {r=r,i=i} end
complex.i=complex.new(0,1)
function M.add(c1,c2)
return M.new(c1.r+c2.r,c1.i+c2.i)
end
3.使用環境
関数環境は興味深い技術であり,モジュールのメインブロックに独占的な環境を持たせることが基本的な考え方である.これにより、そのすべての関数がこの
table
を共有できるだけでなく、そのすべてのグローバル変数もこのM
に記録される.また、すべての公有関数をグローバル変数として生命させることもでき、独立したtable
に自動的に記録されます.モジュールが行うべきことは、このtable
がモジュール名とtable
を付与する次の例である.local modname=...
local M={}
_G[modname]=M
package.loaded[modename]=M
setfenv(1,M)
このとき,関数
table
を宣言すると,彼は以前の形式になった.この環境が変わったはずです.この方法でlocalプロパティを記入するのを忘れても、グローバルネーミングスペースは汚染されません.1つの関数をプライベートから共有に変えるだけです.しかし、もう一つの問題は、他のモジュールにアクセスすることです.空のpackage.loaded
を環境として作成すると、前の環境のグローバル変数にアクセスできません.以下にいくつかの方法を示し,それぞれ長所と短所がある.local modname=...
local M={}
_G[modname]=M
package.loaded[modname]=M
setmetatable(M,{__index=_G})
setfenv(1,M)
add
を呼び出してからtable M
を呼び出す必要があります.この方法により、モジュールは任意のグローバルIDに直接アクセスでき、アクセスするたびにわずかなオーバーヘッドを払うだけです.この結果,このときのモジュールには概念的にすべてのグローバル変数が含まれている.local modname=...
local M={}
_G[modname]=M
package.loaded[modname]=M
local _G=_G
setfenv(1,M)
刺種法はモジュール内に局所変数でグローバルへの参照を保存することに相当し,使用する場合は
setmetatable
を通過すればよい.メタメソッドにアクセスする必要がないため、この方法は上記の方法よりも速い.local modname=...
local M={}
_G[modname]=M
package.loaded[modname]=M
local sqrt=math.sqrt
local io=io
setfenv(1,M)
この方法は,環境を変える前に,すべての内部で必要とされる外部変数を事前に引用し,以前の2つよりも正規である.
4.module関数
前の例では,先頭のコードが似ていることが分かった.
local modname=...
local M={}
_G[modname]=M
package.loaded[modname]=M
setfenv(1,M)
setfenv
の後に、以上の機能を網羅する新しい関数moduleが提供される.モジュールの作成を再開すると、_G.fxx()
でデフォルトを直接完了できます.lua5.1
は外部のアクセスを提供しません.呼び出す前に、アクセスする必要がある外部関数またはモジュールに適切なローカル変数を宣言する必要があります.継承によって外部アクセスを実現することもできます.module(...)
を呼び出すときにオプションmodule
を追加するだけで、module(...,{__index=_G})
module(...,package.seeall)
module
モジュールpackage.seeall
を作成する前に、module
がこのモジュールをすでに含んでいるかどうか、またはモジュールに同じ名前の変数がすでに存在しているかどうかを確認します.table
がこれによってこのpackage.laoded
を見つけた場合、module
はモジュールとして多重化される.yejiushishuo 9は、table
で作成された高速化を開くことができます.