围绕lua和luajit的python包装器

lupa的Python项目详细描述


lupa

logo/logo-220x200.png

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'True
0

还支持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'True
1

对于返回元组的迭代器,例如 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'True
2

注意,从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'True
3

唯一不能工作的地方是在迭代期间,因为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'True
4

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'True
5

注意,在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'True
6

为了简化从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'True
7

第二个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'True
8

查找不存在的键或索引不会返回任何值(实际上 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'True
9

注意 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

有一些限制:

  1. 避免使用lupa.unpacks lua表和lupa.unpacks lua表方法。 对于第一个参数可以是lua表的函数。在这种情况下 py戥func{foo=bar} (与lua中的py戥func({foo=bar})相同) 变得模棱两可:它可能意味着"调用 foo 参数"或"call py_func 带有位置 {foo=bar} 参数"

  2. 在将 nil 值传递给包装在 lupa.unpacks_lua_table lupa.unpacks_lua_table_method decorators。 根据上下文,将 nil 作为参数传递可能意味着 "省略参数"或"不传递参数"。这甚至取决于lua版本。

    可以使用python.none而不是nil来传递none值 坚固耐用。当使用标准大括号时, nil 值的参数也可以 使用func(a,b,c) 语法。

  3. < > >

    由于这些限制,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({})isNoneTrue
0

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({})isNoneTrue
1

导入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({})isNoneTrue
2

欢迎加入QQ群-->: 979659372 Python中文网_新手群

推荐PyPI第三方库


热门话题
空字符串检查在java中未按预期工作   JavaSpringWebClient:自动计算主体的HMAC签名并将其作为头传递   foreach是否有一个Java等效的foreach循环和一个引用变量?   java如何在Eclipse中导入jar   使用特定第三方或java时lombok触发错误。*方法或构造函数   安卓 java将对象数组转换为int数组   java使一定百分比的JUnit测试通过   java Android:将Seekbar的一个值与另一个值进行比较   java将int数组(图像数据)写入文件的最佳方式是什么   java取代了系统。yml的构造函数内的getProperty   sqlite Java将公钥和私钥转换为字符串,然后再转换回字符串   安卓获取白色像素并将其保存到java opencv中的数组中   java为什么是ServerSocket。setSocketFactory静态?   Java数组似乎在不直接修改的情况下更改值