围绕lua和luajit的python包装器
lupa的Python项目详细描述
lupa
lupa将lua或luajit2的运行时集成到cpython中。 这是对 lunaticpython 的部分重写,其中包含一些 附加功能,如正确的协同程序支持。
对于此处未回答的问题,请联系lupa邮件列表。
主要功能
-
通过
lua runtime
类分离lua运行时状态
-
lua协程的python协程包装器
-
对lua中python对象和lua对象的迭代支持
Python
-
字符串的正确编码和解码(每个运行时可配置,
默认为UTF-8)
-
释放gil并支持在单独运行时执行线程
呼叫lua
-
使用Python2.6/3.2及更高版本进行测试
-
为luajit2编写(使用luajit2.0.2测试),但也可以工作
使用普通的lua解释器(5.1和5.2)
-
易于破解和扩展,因为它是用cython编写的,而不是c
为什么叫这个名字?
在拉丁语中,"lupa"是一只母狼,听起来既优雅又狂野。 如果你不喜欢这种直截了当的寓言 濒临灭绝的物种,你也可以高兴地假设它只是 以"lua"和 "巨蟒",两条,保持平衡。
为什么要使用它?
它很好地补充了python。lua是一种动态的语言 python,但是luajit将其编译成非常快的机器代码,有时 比许多计算代码的静态编译语言更快。 语言运行时非常小,并且是为 嵌入。lupa的完整二进制模块,包括静态的 链接的luajit2运行时,在64位计算机上仅重约700kb。 对于标准的lua 5.1,它小于400kb。
然而,lua生态系统缺少python的许多电池 直接包含在其标准库中或作为第三个 派对套餐。这使得真实的lua应用程序更难编写 比同等的python应用程序。因此lua并不常见 用作大型应用程序的主语言,但它使 快速、高级和资源友好的备份语言 需要原始速度和编辑编译运行周期时的python属于 二进制扩展模块对于敏捷来说太重太静态 开发或热部署。
lupa是围绕lua或luajit的一个非常快速和薄的包装。它使它 易于编写的动态lua代码与动态python代码一起 基于权衡,在运行时在两种语言之间切换 在简单和快速之间。
示例
>>>importlupa>>>fromlupaimportLuaRuntime>>>lua=LuaRuntime(unpack_returned_tuples=True)>>>lua.eval('1+1')2>>>lua_func=lua.eval('function(f, n) return f(n) end')>>>defpy_add1(n):returnn+1>>>lua_func(py_add1,2)3>>>lua.eval('python.eval(" 2 ** 2 ")')==4True>>>lua.eval('python.builtins.str(4)')=='4'True
函数 lua_type(obj) 可用于找出 用python代码包装lua对象,由lua的 type()提供 功能:
>>>lupa.lua_type(lua_func)'function'>>>lupa.lua_type(lua.eval('{}'))'table'
帮助区分包装的lua对象和普通的 python对象,它返回 none 对于后者:
>>>lupa.lua_type(123)isNoneTrue>>>lupa.lua_type('abc')isNoneTrue>>>lupa.lua_type({})isNoneTrue
注意传递给create的标志 unpack_returned_tuples=true lua运行时。它在lupa 0.21中是新的,改变了 python函数返回的元组。有了这面旗帜,他们 分解成单独的lua值:
>>>lua.execute('a,b,c = python.eval("(1,2)")')>>>g=lua.globals()>>>g.a1>>>g.b2>>>g.cisNoneTrue
当设置为false时,返回元组的函数将其传递给 Lua代码:
>>>non_explode_lua=lupa.LuaRuntime(unpack_returned_tuples=False)>>>non_explode_lua.execute('a,b,c = python.eval("(1,2)")')>>>g=non_explode_lua.globals()>>>g.a(1,2)>>>g.bisNoneTrue>>>g.cisNoneTrue
因为默认行为(不分解元组)可能在 最新版本的lupa,最好总是显式地传递这个标志。
lua中的python对象
python对象在传递到lua时被转换(例如 数字和字符串)或作为包装对象引用传递。
>>>wrapped_type=lua.globals().type# Lua's own type() function>>>wrapped_type(1)=='number'True>>>wrapped_type('abc')=='string'True
包裹的lua对象在传递回lua时会被解开, 任意的python对象以不同的方式包装:
>>>wrapped_type(wrapped_type)=='function'# unwrapped Lua functionTrue>>>wrapped_type(len)=='userdata'# wrapped Python functionTrue>>>wrapped_type([])=='userdata'# wrapped Python objectTrue
lua支持两种主要的对象协议:调用和索引。它 不区分属性访问和项访问,如 python做了,所以lua操作 obj[x] 和 obj.x 都映射 去索引。决定将哪个python协议用于lua包装 对象,lupa使用简单的启发式方法。
实际上,所有python对象都允许属性访问,因此如果 还有一个 \uu getitem\uu 方法,最好在转动时使用 变成一个可转位的lua对象。否则,它就变成了一个简单的对象 使用属性访问从lua内部进行索引。
显然,这种启发式方法无法提供所需的行为 在许多情况下,例如当需要对对象进行属性访问时 恰好支持项目访问。明确 应该使用的协议,lupa提供了helper函数 as_attrgetter() 和 as_itemgetter() 限制视图打开的 一个特定协议的对象,无论是从python还是从内部 Lua:
>>>lua_func=lua.eval('function(obj) return obj["get"] end')>>>d={'get':'value'}>>>value=lua_func(d)>>>value==d['get']=='value'True>>>value=lua_func(lupa.as_itemgetter(d))>>>value==d['get']=='value'True>>>dict_get=lua_func(lupa.as_attrgetter(d))>>>dict_get==d.getTrue>>>dict_get('get')==d.get('get')=='value'True>>>lua_func=lua.eval(...'function(obj) return python.as_attrgetter(obj)["get"] end')>>>dict_get=lua_func(d)>>>dict_get('get')==d.get('get')=='value'True
注意,与lua函数对象不同,可调用的python对象支持 lua中的索引:
>>>defpy_func():pass>>>py_func.ATTR=2>>>lua_func=lua.eval('function(obj) return obj.ATTR end')>>>lua_func(py_func)2>>>lua_func=lua.eval(...'function(obj) return python.as_attrgetter(obj).ATTR end')>>>lua_func(py_func)2>>>lua_func=lua.eval(...'function(obj) return python.as_attrgetter(obj)["ATTR"] end')>>>lua_func(py_func)2
lua中的迭代
完全支持对lua for循环中的python对象进行迭代。 但是,需要使用 这里描述的实用函数。这与 函数类似于lua中的 pairs() 。
要在普通python iterable上迭代,请使用 python.iter() 功能。例如,可以手动将python列表复制到lua中 这样的桌子:
>>>importlupa>>>fromlupaimportLuaRuntime>>>lua=LuaRuntime(unpack_returned_tuples=True)>>>lua.eval('1+1')2>>>lua_func=lua.eval('function(f, n) return f(n) end')>>>defpy_add1(n):returnn+1>>>lua_func(py_add1,2)3>>>lua.eval('python.eval(" 2 ** 2 ")')==4True>>>lua.eval('python.builtins.str(4)')=='4'True0
还支持python的 enumerate() 函数,因此 可以简化为:
>>>importlupa>>>fromlupaimportLuaRuntime>>>lua=LuaRuntime(unpack_returned_tuples=True)>>>lua.eval('1+1')2>>>lua_func=lua.eval('function(f, n) return f(n) end')>>>defpy_add1(n):returnn+1>>>lua_func(py_add1,2)3>>>lua.eval('python.eval(" 2 ** 2 ")')==4True>>>lua.eval('python.builtins.str(4)')=='4'True1
对于返回元组的迭代器,例如 dict.iteritems() ,它是 使用特殊的 自动将元组项分解为单独的lua参数:
>>>importlupa>>>fromlupaimportLuaRuntime>>>lua=LuaRuntime(unpack_returned_tuples=True)>>>lua.eval('1+1')2>>>lua_func=lua.eval('function(f, n) return f(n) end')>>>defpy_add1(n):returnn+1>>>lua_func(py_add1,2)3>>>lua.eval('python.eval(" 2 ** 2 ")')==4True>>>lua.eval('python.builtins.str(4)')=='4'True2
注意,从lua访问 d.items 方法需要传递 这句名言叫做attrgetter。否则,lua中的属性访问将 使用python dicts的 getitem 协议并查找 d['items'] 取而代之的是
无vs.无
虽然python中的 none 和lua中的 nil 在语义上有所不同,但是 通常都是同样的意思:没有价值。因此lupa试图绘制一个 尽可能直接到另一个:
>>>importlupa>>>fromlupaimportLuaRuntime>>>lua=LuaRuntime(unpack_returned_tuples=True)>>>lua.eval('1+1')2>>>lua_func=lua.eval('function(f, n) return f(n) end')>>>defpy_add1(n):returnn+1>>>lua_func(py_add1,2)3>>>lua.eval('python.eval(" 2 ** 2 ")')==4True>>>lua.eval('python.builtins.str(4)')=='4'True3
唯一不能工作的地方是在迭代期间,因为lua 将a nil 值视为迭代器的终止标记。因此, lupa特殊情况 无 python.none 而不是返回 nil :
>>>importlupa>>>fromlupaimportLuaRuntime>>>lua=LuaRuntime(unpack_returned_tuples=True)>>>lua.eval('1+1')2>>>lua_func=lua.eval('function(f, n) return f(n) end')>>>defpy_add1(n):returnn+1>>>lua_func(py_add1,2)3>>>lua.eval('python.eval(" 2 ** 2 ")')==4True>>>lua.eval('python.builtins.str(4)')=='4'True4
lupa在显然没有必要的时候避免了这个值的转义。 因此,在迭代期间解包元组时,只有第一个值将 必须接受python.none的替换,因为lua不查看 循环终止的其他项目。以及 迭代,已知第一个值总是一个数而不是一个, 因此无需更换。
>>>importlupa>>>fromlupaimportLuaRuntime>>>lua=LuaRuntime(unpack_returned_tuples=True)>>>lua.eval('1+1')2>>>lua_func=lua.eval('function(f, n) return f(n) end')>>>defpy_add1(n):returnn+1>>>lua_func(py_add1,2)3>>>lua.eval('python.eval(" 2 ** 2 ")')==4True>>>lua.eval('python.builtins.str(4)')=='4'True5
注意,在lupa 1.0中,这种行为发生了变化。以前,python.none 更换工作在更多的地方进行,这使得更换工作并非总是很容易预测。
lua表格
lua表模拟python的映射协议。对于特殊情况 数组表,lua自动将整数索引作为键插入 桌子。因此,索引从lua中的1开始,而不是从0开始 就像蟒蛇一样。出于同样的原因,负索引不起作用。 最好将lua表看作映射而不是数组,甚至 对于普通数组表。
>>>importlupa>>>fromlupaimportLuaRuntime>>>lua=LuaRuntime(unpack_returned_tuples=True)>>>lua.eval('1+1')2>>>lua_func=lua.eval('function(f, n) return f(n) end')>>>defpy_add1(n):returnn+1>>>lua_func(py_add1,2)3>>>lua.eval('python.eval(" 2 ** 2 ")')==4True>>>lua.eval('python.builtins.str(4)')=='4'True6
为了简化从python创建表的过程,luaruntime 从python参数创建lua表的助手方法:
>>>importlupa>>>fromlupaimportLuaRuntime>>>lua=LuaRuntime(unpack_returned_tuples=True)>>>lua.eval('1+1')2>>>lua_func=lua.eval('function(f, n) return f(n) end')>>>defpy_add1(n):returnn+1>>>lua_func(py_add1,2)3>>>lua.eval('python.eval(" 2 ** 2 ")')==4True>>>lua.eval('python.builtins.str(4)')=='4'True7
第二个helper方法 .table_from() 是lupa 1.1中的新方法,它接受 任意数量的映射和序列/iterable作为参数。它收集 所有值和键值对,并从中构建一个lua表。 任何出现在多个映射中的键都会被它们的最后一个覆盖 值(从左到右)。
>>>importlupa>>>fromlupaimportLuaRuntime>>>lua=LuaRuntime(unpack_returned_tuples=True)>>>lua.eval('1+1')2>>>lua_func=lua.eval('function(f, n) return f(n) end')>>>defpy_add1(n):returnn+1>>>lua_func(py_add1,2)3>>>lua.eval('python.eval(" 2 ** 2 ")')==4True>>>lua.eval('python.builtins.str(4)')=='4'True8
查找不存在的键或索引不会返回任何值(实际上 nil 在Lua内部)。因此,查找更类似于 .get() python的dict方法,而不是python中的映射查找。
>>>importlupa>>>fromlupaimportLuaRuntime>>>lua=LuaRuntime(unpack_returned_tuples=True)>>>lua.eval('1+1')2>>>lua_func=lua.eval('function(f, n) return f(n) end')>>>defpy_add1(n):returnn+1>>>lua_func(py_add1,2)3>>>lua.eval('python.eval(" 2 ** 2 ")')==4True>>>lua.eval('python.builtins.str(4)')=='4'True9
注意 len() 对数组表做了正确的事情,但是没有 处理映射:
>>>lupa.lua_type(lua_func)'function'>>>lupa.lua_type(lua.eval('{}'))'table'0
这是因为 len() 是基于 因为lua定义表长度的方式。 记住,未设置的表索引总是返回nil,包括 表大小之外的索引。因此,lua基本上寻找 返回 nil 并在此之前返回索引的索引。这个 对于不包含 nil 值的数组表,可以使用 对于带有"孔"且不起作用的表,几乎无法预测结果 完全是为了映射表。对于同时具有顺序和 映射内容,完全忽略映射部分。
注意,最好不要依赖len()的行为 映射。它可能会在lupa的更高版本中更改。
与lua提供的表接口类似,lupa也支持 对表成员的属性访问:
>>>lupa.lua_type(lua_func)'function'>>>lupa.lua_type(lua.eval('{}'))'table'1
这允许访问与表关联的lua"方法", 如标准库模块所用:
>>>lupa.lua_type(lua_func)'function'>>>lupa.lua_type(lua.eval('{}'))'table'2
python可调用函数
如前所述,lupa允许lua脚本调用python函数 方法:
>>>lupa.lua_type(lua_func)'function'>>>lupa.lua_type(lua.eval('{}'))'table'3
lua没有用于命名参数的专用语法,因此默认情况下 只能使用位置参数调用python可调用项。
在lua中实现命名参数的一个常见模式是传递它们 作为第一个也是唯一的函数参数。见 http://lua-users.org/wiki/namedparameterers 了解更多详细信息。Lupa支架 这个模式通过提供两个decorator来实现: lupa.unpacks\u lua\u table 对于python函数和方法 python对象的。
包装在这些装饰器中的python函数/方法可以从 lua代码为 func(foo,bar) , func{foo=foo,bar=bar} 或 函数{foo,bar=bar} 。例子:
>>>lupa.lua_type(lua_func)'function'>>>lupa.lua_type(lua.eval('{}'))'table'4
如果不控制函数实现,也可以 在将可调用对象传递到lupa时手动包装它:
>>>lupa.lua_type(lua_func)'function'>>>lupa.lua_type(lua.eval('{}'))'table'5
有一些限制:
避免使用lupa.unpacks lua表和lupa.unpacks lua表方法。 对于第一个参数可以是lua表的函数。在这种情况下 py戥func{foo=bar} (与lua中的py戥func({foo=bar})相同) 变得模棱两可:它可能意味着"调用 foo 参数"或"call py_func 带有位置 {foo=bar} 参数"
在将 nil 值传递给包装在 lupa.unpacks_lua_table 或 lupa.unpacks_lua_table_method decorators。 根据上下文,将 nil 作为参数传递可能意味着 "省略参数"或"不传递参数"。这甚至取决于lua版本。
可以使用python.none而不是nil来传递none值 坚固耐用。当使用标准大括号时, nil 值的参数也可以 使用func(a,b,c) 语法。
< > >
由于这些限制,lupa不能为所有人启用命名参数 python可自动调用。decorators允许启用命名参数 以每次可呼叫为基础。
lua协同活动
下一个是lua协程的例子。包扎好的lua协游 行为完全像一条蟒蛇。它需要在 开始,可以使用 函数或通过在lua代码中创建它。然后,可以将值发送到 它使用 .send() 方法,或者可以迭代。注意 但是,不支持 .throw() 方法。
>>>lupa.lua_type(lua_func)'function'>>>lupa.lua_type(lua.eval('{}'))'table'6
一个使用 .send() 方法:
>>>lupa.lua_type(lua_func)'function'>>>lupa.lua_type(lua.eval('{}'))'table'7
它还可以在lua中创建协程并将它们传递回 python空间:
>>>lupa.lua_type(lua_func)'function'>>>lupa.lua_type(lua.eval('{}'))'table'8
线程化
以下示例并行计算Mandelbrot图像 线程并以pil格式显示结果。它基于a 基准 实现 计算机语言基准测试游戏
>>>lupa.lua_type(lua_func)'function'>>>lupa.lua_type(lua.eval('{}'))'table'9
注意示例如何为每个线程创建单独的 luaruntime 以启用并行执行。每个 luaruntime 受 防止并发访问它的全局锁。内存不足 lua的足迹使得使用多个运行时是合理的,但是 此设置还意味着值不能在 lua内部的线程。它们要么通过python复制 空格(传递表引用也不起作用)或使用一些lua 用于显式通信的机制,如管道或某种 共享内存设置。
限制lua对python对象的访问
lupa提供了一种简单的机制来控制对python对象的访问。 每个属性访问都可以通过一个过滤器函数作为 以下:
>>>lupa.lua_type(123)isNoneTrue>>>lupa.lua_type('abc')isNoneTrue>>>lupa.lua_type({})isNoneTrue0
is_设置标志表示属性是否被阅读 或设置。
注意,python函数的属性提供了对 当前 globals() 因此,如果您想 为了安全地限制对一组已知python对象的访问,最好 使用安全属性名的白名单。一种方法 可以是使用精心选择的专用api对象列表 提供给lua代码,并且只允许python属性访问 这些对象的公共属性/方法名集。
从lupa 1.0开始,您可以提供专用的getter和 luaruntime的setter函数实现:
>>>lupa.lua_type(123)isNoneTrue>>>lupa.lua_type('abc')isNoneTrue>>>lupa.lua_type({})isNoneTrue1
导入lua二进制模块
这通常按原样工作,但以下是详细信息,以防 你出了什么问题。
要在lua中使用二进制模块,需要根据 用于构建lupa的luajit源的头文件,但是 不要将它们链接到Luajit库。
此外,cpython需要为 加载lupa模块之前共享库。这可以通过 调用sys.setdlopenflags(标志值) 。导入 lupa 模块将自动尝试设置正确的 dlopen 标志 如果它能找到特定于平台的 定义必要的标志常量。在这种情况下,使用二进制 lua中的模块应该可以开箱即用。
但是,如果此设置失败,则必须手动设置标志。 使用上述配置调用时,参数 标志值 必须表示 rtld_new 和 rtld_全局 。如果 rtld_new 为2且 rtld_global 为256,则 需要调用sys.setdlopenflags(258)
假设lua luapoSix ( posix )模块可用,则 在Linux系统上应该可以使用以下命令:
>>>lupa.lua_type(123)isNoneTrue>>>lupa.lua_type('abc')isNoneTrue>>>lupa.lua_type({})isNoneTrue2