PythonでLuaを呼び出すときにハマったところ

今回はPythonのLua統合ランタイムのパッケージであるLupaを使う上で個人的にハマった部分を解説していきます。
2023.09.29

Luaについて

Luaは軽量なスクリプト言語です。Luaはポータビリティと拡張性を重視して設計されており、他の言語から呼び出しやすいです。今回はPythonでLuaを呼び出せるlupaを使用してみます。

参考: Lua - Wikipedia

Lupa

今回はLupaというパッケージを使用してLuaをPythonから呼び出してみます。

基本的な使い方は以下のリポジトリのReadmeに書いてあるので省きます。 今回は、Lupaを使う上で個人的に分かりにくかったポイントを重点的に解説していきます。

参考: scoder/lupa: Lua in Python

使い方

基本的な使い方

sample.py

import lupa
from lupa import LuaRuntime

lua = LuaRuntime(unpack_returned_tuples=True) # ランタイムを作成

print(lua.lua_implementation)
# => Lua 5.4
print(lua.eval('1+1')) # evalメソッドでインラインでLuaのコードを実行可能
# => 2

基本的な使い方はLuaのランタイムを作成し、それを使ってLuaのコードを評価していくといった流れです。 ちなみに、ランタイムはLuaJITにも対応しています。 自分の環境ではLua5.4が使用されていました。

スクリプトのロード

Lupaではrequireメソッドを利用して別ファイルで定義されたスクリプトを呼び出すことも可能です。

sample.py

sum, path = lua.require('sum')
print(path)
# => ./sum.lua

t = lua.table_from([1, 2, 3, 4])
print(sum(t))
# => 10

requireはスクリプトの返り値とそのパスを返します。スクリプトを呼び出すときはパスではなくLuaのrequireと同じ値で呼び出します。(require(./sum.lua)ではエラーになる)

スクリプトの中身は以下のような感じです。

sum.lua

function sum(list)
    local ret = 0
    for _, v in ipairs(list) do
        ret = ret + v
    end
    return ret
end

return sum

コルーチンの呼び出し

Luaのコルーチンを呼んでみます。 今回は値を足していくような関数をLuaで用意しました。

acc.lua

function accumulate()
    local val = coroutine.yield()
    while true do
        local new_val = coroutine.yield(val)
        val = val + new_val
    end
end

return accumulate

sample.py

acc, path = lua.require('acc')
co = acc.coroutine()
co.send(None)
for i in range(4):
    print(co.send(i))
    # => 0
    #    1
    #    3
    #    6

Lupaでは関数オブジェクトにはcoroutineメソッドが実装されており、そこからコルーチンオブジェクトを作成可能です。 coroutineメソッドの引数は関数の引数になります。

その後、作成されたコルーチンオブジェクトのsendメソッドを使用することでコルーチンとの値のやり取りが可能になります。 最初にNoneを渡してあげるとコルーチンがスタートします。

OOP

Luaではクラスのようなものを書くことも可能です。

今回はDogという簡単なクラスを用意しました。

dog.lua

local Dog = {}

function Dog:new(o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    return o
end

function Dog:bow()
    return self.word
end

function Dog:multi_bow(num)
    for i = 1, num do
        coroutine.yield(i .. ": " .. self.word)
    end
end

return Dog

sample.py

dog, path = lua.require('dog')
buddy = dog.new(dog, lua.table_from({"word": "I'm buddy."}))
print(dog.bow(buddy))
# => I'm buddy.

co = dog.multi_bow.coroutine(buddy, 3)
while True:
    try:
        print(co.send(None))
        # => 1: I'm buddy.
        #    2: I'm buddy.
        #    3: I'm buddy.
    except StopIteration:
        break

ここで注目すべきはメソッドの呼び出し方です。 Luaの:オペレータは隠れている第一引数のselfを省略するように働きます。 Lupaではこの省略を省けないので毎回、自分自身を第一引数に渡して上げる必要があります。

イメージとしては以下のような感じです。

:オペレータ

function Dog:bow() <=> function Dog.bow(self)

これはコルーチンオブジェクトを作成する場合も同じです。 ただし、作成されたコルーチンオブジェクトには自分自身を渡す必要はありません。

最後に

今回はLupaを使う上で個人的に詰まった部分を解説しました。 PythonからLuaを呼び出す上でとても便利なパッケージだと思います。