lua入門ノート4環境モジュールとパッケージ

9479 ワード

1環境
我々がluaで使用するすべてのグローバル変数は、環境(environment)と呼ばれる従来のtableに保存されている.彼はtableなので、他のtableを操作するように操作することができます.このような動作を実施するために、tableは、環境lua自身をグローバル変数tableに保存する.たとえば、
for n in pairs(_G) do
  ptinr(n)
end
  • 1. 動的な名前を持つグローバル変数
  • グローバル変数にアクセスしたり設定したりする必要がある場合、メタプログラミングの形式も使用されます.例えば次の操作
    value=loadstring("return "..varname)()  --varname:    
    

    実際には次の方法で直接アクセスできます
    _G[varname]=value
    
  • 2. グローバル変数宣言
  • グローバル変数は通常の_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
  • 3. 非大域的な環境
  • 以前の環境のもう一つの大きな問題は彼が全局的であることにある.ローカル変更後、グローバルは有効になります.たとえば、グローバルメタテーブルを上の制御グローバル変数に設定してアクセスを宣言すると、プログラムにこの仕様を守らない場所があると、プログラムにエラーが発生します.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)
    

    このコードでは、新しい環境は元の環境から_Gprintを継承していますが、任意の付与は新しい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.preladopreload tableファイルを見つけた場合、彼はrequireを通じてファイルをロードします.一方、Cライブラリが見つかった場合は、luaでロードされます.どちらの関数もコードをロードしただけで、実行されません.コードを実行するために、loadfileは、これらのコードをモジュール名をパラメータとして呼び出す.ローダに戻り値がある場合、loadlib九江という戻り値はrequireに格納され、戻り値がない場合、requiretable 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で作成された高速化を開くことができます.