跳转到主要内容

Python的Lua和LuaJIT封装器

项目描述

Lupa

logo/logo-220x200.png

Lupa将Lua或LuaJIT2的运行时集成到CPython中。它是使用Cython重写的LunaticPython的部分重写,增加了一些额外的功能,如适当的协程支持。

对于这里没有回答的问题,请通过Lupa邮件列表联系。

主要特性

  • 通过LuaRuntime类分离Lua运行时状态

  • Python协程封装器用于Lua协程

  • 支持在Lua和Python中迭代Python对象和Lua对象

  • 正确编码和解码字符串(每个运行时可配置,默认为UTF-8)

  • 释放 GIL 并在调用 Lua 时支持在不同运行时中进行多线程。

  • 经过 Python 2.7/3.6 及以上版本的测试。

  • 包含 Lua 5.1、5.2、5.3 和 5.4 以及 LuaJIT 2.0 和 2.1,适用于支持这些版本的系统。

  • 由于其使用 Cython 而不是 C 编写,因此易于修改和扩展。

为什么叫这个名字?

在拉丁语中,“lupa” 是母狼,正如其声音一样优雅而野生。如果您不喜欢这种直接将濒危物种寓言化的方式,您也可以愉快地假设它只是“Lua”和“Python”这两个词的音节组合,每个词取两个字以保持平衡。

为什么使用它?

Lupa 与 Python 非常兼容。Lua 是一种与 Python 一样动态的语言,但 LuaJIT 将其编译成非常快速的机器代码,有时甚至比许多静态编译的计算语言更快。语言运行时非常小,并精心设计用于嵌入。Lupa 的完整二进制模块,包括静态链接的 LuaJIT2 运行时,在 64 位机器上仅重约 800KB。使用标准 Lua 5.2,它不到 600KB。

然而,Lua 生态系统缺乏 Python 标准库中直接包含的大量电池,或者作为第三方包。这使得实际 Lua 应用程序的编写比等效的 Python 应用程序更困难。因此,Lua 通常不作为大型应用程序的主要语言,但在需要原始速度且二进制扩展模块的编辑-编译-运行周期过于沉重和静态以适应敏捷开发或热部署时,Lua 可以作为 Python 中的快速、高级且资源友好的备用语言。

Lupa 是 Lua 或 LuaJIT 的非常快速和轻量级包装器。它使得编写动态 Lua 代码变得容易,该代码可以通过在运行时在两种语言之间切换来伴随动态 Python 代码,基于简单性和速度之间的权衡。

哪个Lua版本?

如果支持,二进制轮子包括不同的 Lua 版本以及 LuaJIT。默认情况下,import lupa 使用最新的 Lua 版本,但您可以通过导入选择一个特定的版本。

try:
    import lupa.luajit21 as lupa
except ImportError:
    try:
        import lupa.lua54 as lupa
    except ImportError:
        try:
            import lupa.lua53 as lupa
        except ImportError:
            import lupa

print(f"Using {lupa.LuaRuntime().lua_implementation} (compiled with {lupa.LUA_VERSION})")

示例

>>> from lupa.lua54 import LuaRuntime
>>> lua = LuaRuntime(unpack_returned_tuples=True)

>>> lua.eval('1+1')
2

>>> lua_func = lua.eval('function(f, n) return f(n) end')

>>> def py_add1(n): return n+1
>>> lua_func(py_add1, 2)
3

>>> lua.eval('python.eval(" 2 ** 2 ")') == 4
True
>>> 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) is None
True
>>> lupa.lua_type('abc') is None
True
>>> lupa.lua_type({}) is None
True

注意传递给创建 Lua 运行时的标志 unpack_returned_tuples=True。这是 Lupa 0.21 中引入的,并改变了 Python 函数返回的元组的行为。使用此标志,它们会分解成单独的 Lua 值。

>>> lua.execute('a,b,c = python.eval("(1,2)")')
>>> g = lua.globals()
>>> g.a
1
>>> g.b
2
>>> g.c is None
True

当设置为 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.b is None
True
>>> g.c is None
True

由于 Lupa 的默认行为(不分解元组)可能在未来的版本中更改,因此最好始终显式传递此标志。

Python对象在Lua中

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 function
True
>>> wrapped_type(len) == 'userdata'       # wrapped Python function
True
>>> wrapped_type([]) == 'userdata'        # wrapped Python object
True

Lua 支持两种主要对象协议:调用和索引。它与 Python 不同的地方在于它不区分属性访问和项访问,因此 Lua 操作 obj[x]obj.x 都映射到索引。为了决定为 Lua 包装对象使用哪种 Python 协议,Lupa 采用了一种简单的启发式方法。

实际上所有 Python 对象都允许属性访问,因此如果对象还具有 __getitem__ 方法,则在将其转换为可索引的 Lua 对象时优先考虑。否则,它将成为一个简单的对象,在 Lua 内部使用属性访问进行索引。

显然,这种方法在许多情况下都无法提供所需的行为,例如当需要访问一个恰好支持项目访问的对象的属性时。为了明确应使用的协议,Lupa提供了辅助函数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.get
True
>>> 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中支持索引。

>>> def py_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对象是全面支持的。然而,Python可迭代对象需要使用这里描述的其中一个实用函数进行转换。这类似于Lua中的pairs()函数。

要遍历一个简单的Python可迭代对象,请使用python.iter()函数。例如,您可以手动将Python列表复制到Lua表,如下所示:

>>> lua_copy = lua.eval('''
...     function(L)
...         local t, i = {}, 1
...         for item in python.iter(L) do
...             t[i] = item
...             i = i + 1
...         end
...         return t
...     end
... ''')

>>> table = lua_copy([1,2,3,4])
>>> len(table)
4
>>> table[1]   # Lua indexing
1

Python的enumerate()函数也受到支持,因此上述代码可以简化为:

>>> lua_copy = lua.eval('''
...     function(L)
...         local t = {}
...         for index, item in python.enumerate(L) do
...             t[ index+1 ] = item
...         end
...         return t
...     end
... ''')

>>> table = lua_copy([1,2,3,4])
>>> len(table)
4
>>> table[1]   # Lua indexing
1

对于返回元组的迭代器,例如dict.iteritems(),可以使用特殊的python.iterex()函数,该函数自动将元组项展开为单独的Lua参数。

>>> lua_copy = lua.eval('''
...     function(d)
...         local t = {}
...         for key, value in python.iterex(d.items()) do
...             t[key] = value
...         end
...         return t
...     end
... ''')

>>> d = dict(a=1, b=2, c=3)
>>> table = lua_copy( lupa.as_attrgetter(d) )
>>> table['b']
2

注意,从Lua访问d.items方法需要传递字典作为attrgetter。否则,Lua中的属性访问将使用Python字典的getitem协议,并查找d['items']

None与nil

虽然Python中的None和Lua中的nil在语义上有所不同,但它们通常意味着同一件事:没有值。因此,Lupa尽可能地直接将一个映射到另一个。

>>> lua.eval('nil') is None
True
>>> is_nil = lua.eval('function(x) return x == nil end')
>>> is_nil(None)
True

这不能工作的情况仅发生在迭代过程中,因为Lua将nil值视为迭代器的终止标记。因此,Lupa在这里特别处理None值,并用常量python.none替换它们,而不是返回nil

>>> _ = lua.require("table")
>>> func = lua.eval('''
...     function(items)
...         local t = {}
...         for value in python.iter(items) do
...             table.insert(t, value == python.none)
...         end
...         return t
...     end
... ''')

>>> items = [1, None ,2]
>>> list(func(items).values())
[False, True, False]

Lupa在显然不必要时避免这种值逃逸。因此,在迭代过程中解包元组时,只有第一个值将受到python.none替换的影响,因为Lua不再检查其他项目以确定循环的终止。在enumerate()迭代中,第一个值已知始终是数字且永远不会是None,因此不需要替换。

>>> func = lua.eval('''
...     function(items)
...         for a, b, c, d in python.iterex(items) do
...             return {a == python.none, a == nil,   -->  a == python.none
...                     b == python.none, b == nil,   -->  b == nil
...                     c == python.none, c == nil,   -->  c == nil
...                     d == python.none, d == nil}   -->  d == nil ...
...         end
...     end
... ''')

>>> items = [(None, None, None, None)]
>>> list(func(items).values())
[True, False, False, True, False, True, False, True]

>>> items = [(None, None)]   # note: no values for c/d => nil in Lua
>>> list(func(items).values())
[True, False, False, True, False, True, False, True]

注意,这种行为在Lupa 1.0中发生了变化。以前,python.none替换在更多地方执行,这使得它不是总是非常有预测性。

Lua表

Lua表模拟Python的映射协议。对于数组表的特殊情况,Lua自动将整数索引作为键插入到表中。因此,索引从1开始,而不是Python中的0。由于同样的原因,负索引不起作用。对于纯数组表,最好将Lua表视为映射而不是数组。

>>> table = lua.eval('{10,20,30,40}')
>>> table[1]
10
>>> table[4]
40
>>> list(table)
[1, 2, 3, 4]
>>> dict(table)
{1: 10, 2: 20, 3: 30, 4: 40}
>>> list(table.values())
[10, 20, 30, 40]
>>> len(table)
4

>>> mapping = lua.eval('{ [1] = -1 }')
>>> list(mapping)
[1]

>>> mapping = lua.eval('{ [20] = -20; [3] = -3 }')
>>> mapping[20]
-20
>>> mapping[3]
-3
>>> sorted(mapping.values())
[-20, -3]
>>> sorted(mapping.items())
[(3, -3), (20, -20)]

>>> mapping[-3] = 3     # -3 used as key, not index!
>>> mapping[-3]
3
>>> sorted(mapping)
[-3, 3, 20]
>>> sorted(mapping.items())
[(-3, 3), (3, -3), (20, -20)]

为了简化从Python创建表,LuaRuntime提供了一个辅助方法,可以从Python参数创建Lua表。

>>> t = lua.table(10, 20, 30, 40)
>>> lupa.lua_type(t)
'table'
>>> list(t)
[1, 2, 3, 4]
>>> list(t.values())
[10, 20, 30, 40]

>>> t = lua.table(10, 20, 30, 40, a=1, b=2)
>>> t[3]
30
>>> t['b']
2

Lupa 1.1中添加了第二个辅助方法,.table_from(),它接受任意数量的映射和序列/可迭代对象作为参数。它收集所有值和键值对,并从它们构建单个Lua表。任何在多个映射中出现的键都会用它们的最后一个值覆盖(从左到右)。

>>> t = lua.table_from([10, 20, 30], {'a': 11, 'b': 22}, (40, 50), {'b': 42})
>>> t['a']
11
>>> t['b']
42
>>> t[5]
50
>>> sorted(t.values())
[10, 11, 20, 30, 40, 42, 50]

从Lupa 2.1开始,传递recursive=True将递归地将数据结构映射到Lua表。

>>> t = lua.table_from(
...     [
...         # t1:
...         [
...             [10, 20, 30],
...             {'a': 11, 'b': 22}
...         ],
...         # t2:
...         [
...             (40, 50),
...             {'b': 42}
...         ]
...     ],
...     recursive=True
... )
>>> t1, t2 = t.values()
>>> list(t1[1].values())
[10, 20, 30]
>>> t1[2]['a']
11
>>> t1[2]['b']
22
>>> t2[2]['b']
42
>>> list(t1[1].values())
[10, 20, 30]
>>> list(t2[1].values())
[40, 50]

查找不存在的键或索引将返回 None(实际上在 Lua 中是 nil)。因此,查找方法与 Python 字典的 .get() 方法更相似,而不是与 Python 中的映射查找相似。

>>> table = lua.table(10, 20, 30, 40)
>>> table[1000000] is None
True
>>> table['no such key'] is None
True

>>> mapping = lua.eval('{ [20] = -20; [3] = -3 }')
>>> mapping['no such key'] is None
True

请注意,len() 对数组表做正确的事,但不能用于映射。

>>> len(table)
4
>>> len(mapping)
0

这是因为 len() 基于 Lua 中的 #(长度)操作符,以及 Lua 对表长度的定义。记住,未设置的表索引始终返回 nil,包括表大小之外的索引。因此,Lua 实际上是在寻找一个返回 nil 的索引,并返回其之前的索引。这对于不包含 nil 值的数组表来说效果很好,但对于有“空洞”的表来说几乎不可预测,对于映射表则完全不工作。对于既有顺序内容又有映射内容的表,这完全忽略了映射部分。

请注意,最好不要依赖于 len() 在映射中的行为。它在 Lupa 的后续版本中可能会改变。

Lupa 与 Lua 提供的表接口类似,也支持对表成员的属性访问

>>> table = lua.eval('{ a=1, b=2 }')
>>> table.a, table.b
(1, 2)
>>> table.a == table['a']
True

这允许访问与表关联的 Lua “方法”,如标准库模块所使用的那样

>>> string = lua.eval('string')    # get the 'string' library table
>>> print( string.lower('A') )
a

Python可调用对象

如前所述,Lupa 允许 Lua 脚本调用 Python 函数和方法

>>> def add_one(num):
...     return num + 1
>>> lua_func = lua.eval('function(num, py_func) return py_func(num) end')
>>> lua_func(48, add_one)
49

>>> class MyClass():
...     def my_method(self):
...         return 345
>>> obj = MyClass()
>>> lua_func = lua.eval('function(py_obj) return py_obj:my_method() end')
>>> lua_func(obj)
345

Lua 没有专门用于命名参数的语法,所以默认情况下,Python 可调用对象只能使用位置参数来调用。

在 Lua 中实现命名参数的常见模式是将它们作为一个表作为第一个也是唯一的功能参数传递。有关详细信息,请参阅 https://lua-users.lua.ac.cn/wiki/NamedParameters。Lupa 通过提供两个装饰器来支持这种模式:lupa.unpacks_lua_table 用于 Python 函数和 lupa.unpacks_lua_table_method 用于 Python 对象的方法。

使用这些装饰器包装的 Python 函数/方法可以从 Lua 代码中以 func(foo, bar)func{foo=foo, bar=bar}func{foo, bar=bar} 的方式调用。示例

>>> @lupa.unpacks_lua_table
... def add(a, b):
...     return a + b
>>> lua_func = lua.eval('function(a, b, py_func) return py_func{a=a, b=b} end')
>>> lua_func(5, 6, add)
11
>>> lua_func = lua.eval('function(a, b, py_func) return py_func{a, b=b} end')
>>> lua_func(5, 6, add)
11

如果您无法控制函数的实现,也可以在将其传递给 Lupa 时手动包装可调用对象

>>> import operator
>>> wrapped_py_add = lupa.unpacks_lua_table(operator.add)

>>> lua_func = lua.eval('function(a, b, py_func) return py_func{a, b} end')
>>> lua_func(5, 6, wrapped_py_add)
11

有一些限制

  1. 避免在第一个参数可以是 Lua 表的函数中使用 lupa.unpacks_lua_tablelupa.unpacks_lua_table_method 装饰器。在这种情况下,py_func{foo=bar}(在 Lua 中与 py_func({foo=bar}) 相同)变得含糊不清:它可以意味着“调用 py_func 并带有命名参数 foo”或“调用 py_func 并带有位置参数 {foo=bar}”。

  2. 在使用 lupa.unpacks_lua_tablelupa.unpacks_lua_table_method 装饰器包装的可调用对象传递时,应小心处理 nil 值。根据上下文,将 nil 作为参数传递可以表示“省略参数”或“传递 None”。这甚至取决于 Lua 版本。

    可以使用 python.none 而不是 nil 来安全地传递 None 值。当使用标准括号语法 func(a, b, c) 时,带有 nil 值的参数也是可以的。

由于这些限制,lupa 不会自动为所有 Python 可调用对象启用命名参数。装饰器允许基于每个可调用对象启用命名参数。

Lua协程

下面是Lua协程的一个示例。一个封装的Lua协程表现得就像Python协程一样。它需要在开始时创建,可以通过使用函数的.coroutine()方法或通过在Lua代码中创建它来实现。然后,可以使用.send()方法向其中发送值,或者对其进行迭代。注意,.throw()方法不受支持。

>>> lua_code = '''\
...     function(N)
...         for i=0,N do
...             coroutine.yield( i%2 )
...         end
...     end
... '''
>>> lua = LuaRuntime()
>>> f = lua.eval(lua_code)

>>> gen = f.coroutine(4)
>>> list(enumerate(gen))
[(0, 0), (1, 1), (2, 0), (3, 1), (4, 0)]

以下是一个使用.send()方法将值传递到协程中的示例

>>> lua_code = '''\
...     function()
...         local t,i = {},0
...         local value = coroutine.yield()
...         while value do
...             t[i] = value
...             i = i + 1
...             value = coroutine.yield()
...         end
...         return t
...     end
... '''
>>> f = lua.eval(lua_code)

>>> co = f.coroutine()   # create coroutine
>>> co.send(None)        # start coroutine (stops at first yield)

>>> for i in range(3):
...     co.send(i*2)

>>> mapping = co.send(None)   # loop termination signal
>>> sorted(mapping.items())
[(0, 0), (1, 2), (2, 4)]

它也可以在Lua中创建协程并将它们传回到Python空间

>>> lua_code = '''\
...   function f(N)
...         for i=0,N do
...             coroutine.yield( i%2 )
...         end
...   end ;
...   co1 = coroutine.create(f) ;
...   co2 = coroutine.create(f) ;
...
...   status, first_result = coroutine.resume(co2, 2) ;   -- starting!
...
...   return f, co1, co2, status, first_result
... '''

>>> lua = LuaRuntime()
>>> f, co, lua_gen, status, first_result = lua.execute(lua_code)

>>> # a running coroutine:

>>> status
True
>>> first_result
0
>>> list(lua_gen)
[1, 0]
>>> list(lua_gen)
[]

>>> # an uninitialised coroutine:

>>> gen = co(4)
>>> list(enumerate(gen))
[(0, 0), (1, 1), (2, 0), (3, 1), (4, 0)]

>>> gen = co(2)
>>> list(enumerate(gen))
[(0, 0), (1, 1), (2, 0)]

>>> # a plain function:

>>> gen = f.coroutine(4)
>>> list(enumerate(gen))
[(0, 0), (1, 1), (2, 0), (3, 1), (4, 0)]

多线程

以下示例在并行线程中计算Mandelbrot图像并使用PIL显示结果。它基于基准实现,用于计算机语言基准游戏

lua_code = '''\
    function(N, i, total)
        local char, unpack = string.char, table.unpack
        local result = ""
        local M, ba, bb, buf = 2/N, 2^(N%8+1)-1, 2^(8-N%8), {}
        local start_line, end_line = N/total * (i-1), N/total * i - 1
        for y=start_line,end_line do
            local Ci, b, p = y*M-1, 1, 0
            for x=0,N-1 do
                local Cr = x*M-1.5
                local Zr, Zi, Zrq, Ziq = Cr, Ci, Cr*Cr, Ci*Ci
                b = b + b
                for i=1,49 do
                    Zi = Zr*Zi*2 + Ci
                    Zr = Zrq-Ziq + Cr
                    Ziq = Zi*Zi
                    Zrq = Zr*Zr
                    if Zrq+Ziq > 4.0 then b = b + 1; break; end
                end
                if b >= 256 then p = p + 1; buf[p] = 511 - b; b = 1; end
            end
            if b ~= 1 then p = p + 1; buf[p] = (ba-b)*bb; end
            result = result .. char(unpack(buf, 1, p))
        end
        return result
    end
'''

image_size = 1280   # == 1280 x 1280
thread_count = 8

from lupa import LuaRuntime
lua_funcs = [ LuaRuntime(encoding=None).eval(lua_code)
              for _ in range(thread_count) ]

results = [None] * thread_count
def mandelbrot(i, lua_func):
    results[i] = lua_func(image_size, i+1, thread_count)

import threading
threads = [ threading.Thread(target=mandelbrot, args=(i,lua_func))
            for i, lua_func in enumerate(lua_funcs) ]
for thread in threads:
    thread.start()
for thread in threads:
    thread.join()

result_buffer = b''.join(results)

# use Pillow to display the image
from PIL import Image
image = Image.frombytes('1', (image_size, image_size), result_buffer)
image.show()

注意示例为每个线程创建一个单独的LuaRuntime以实现并行执行。每个LuaRuntime都由一个全局锁保护,防止并发访问。Lua的低内存占用使其合理地使用多个运行时,但此设置也意味着Lua内部的线程之间不能轻易交换值。它们必须通过Python空间复制(传递表引用也不会工作)或使用某种Lua机制进行显式通信,例如管道或某种类型的共享内存设置。

限制Lua对Python对象的访问

Lupa提供了一个简单的机制来控制对Python对象的访问。每个属性访问都可以通过以下方式传递给过滤器函数

>>> def filter_attribute_access(obj, attr_name, is_setting):
...     if isinstance(attr_name, unicode):
...         if not attr_name.startswith('_'):
...             return attr_name
...     raise AttributeError('access denied')

>>> lua = lupa.LuaRuntime(
...           register_eval=False,
...           attribute_filter=filter_attribute_access)
>>> func = lua.eval('function(x) return x.__class__ end')
>>> func(lua)
Traceback (most recent call last):
 ...
AttributeError: access denied

is_setting标志指示属性是正在读取还是设置。

请注意,Python函数的属性提供了对当前globals()的访问,因此可以访问内置函数等。如果您想安全地限制对一组已知Python对象的访问,最好使用安全属性名的白名单。一种方法可以是使用精心选择的专用API对象列表,将其提供给Lua代码,并且仅允许Python属性访问这些对象的公共属性/方法名称集。

从Lupa 1.0开始,您可以提供为LuaRuntime指定的专用获取器和设置器函数实现

>>> def getter(obj, attr_name):
...     if attr_name == 'yes':
...         return getattr(obj, attr_name)
...     raise AttributeError(
...         'not allowed to read attribute "%s"' % attr_name)

>>> def setter(obj, attr_name, value):
...     if attr_name == 'put':
...         setattr(obj, attr_name, value)
...         return
...     raise AttributeError(
...         'not allowed to write attribute "%s"' % attr_name)

>>> class X(object):
...     yes = 123
...     put = 'abc'
...     noway = 2.1

>>> x = X()

>>> lua = lupa.LuaRuntime(attribute_handlers=(getter, setter))
>>> func = lua.eval('function(x) return x.yes end')
>>> func(x)  # getting 'yes'
123
>>> func = lua.eval('function(x) x.put = "ABC"; end')
>>> func(x)  # setting 'put'
>>> print(x.put)
ABC
>>> func = lua.eval('function(x) x.noway = 42; end')
>>> func(x)  # setting 'noway'
Traceback (most recent call last):
 ...
AttributeError: not allowed to write attribute "noway"

限制Lua内存使用

Lupa自2.0版本起提供了一个简单的机制来控制Lua运行时的最大内存使用。默认情况下,Lupa不会干扰Lua的内存分配,要启用它,必须在创建LuaRuntime时设置max_memory

LuaRuntime提供了三种用于控制和读取内存使用的方法

  1. get_memory_used(total=False)用于获取LuaRuntime当前内存使用量。

  2. get_max_memory(total=False)用于获取当前内存限制。0表示没有内存限制。

  3. set_max_memory(max_memory, total=False)用于更改内存限制。值低于或等于0表示没有限制。

LuaRuntime本身始终会占用一些内存(大约~20KiB,取决于您的Lua版本和其他因素),在所有计算中都被排除在外,除非您指定total=True

>>> from lupa import lua52
>>> lua = lua52.LuaRuntime(max_memory=0)  # 0 for unlimited, default is None
>>> lua.get_memory_used()  # memory used by your code
0
>>> total_lua_memory = lua.get_memory_used(total=True)  # includes memory used by the runtime itself
>>> assert total_lua_memory > 0  # exact amount depends on your lua version and other factors

达到内存限制的Lua代码将收到内存错误

>>> lua.set_max_memory(100)
>>> lua.eval("string.rep('a', 1000)")   # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
 ...
lupa.LuaMemoryError: not enough memory

LuaMemoryError继承自LuaErrorMemoryError

导入Lua二进制模块

这通常按原样工作,但这里有一些细节,以防您遇到任何问题。

要在Lua中使用二进制模块,您需要将它们编译与构建Lupa时使用的LuaJIT源代码的头文件相匹配,但不要将它们链接到LuaJIT库。

此外,在加载Lupa模块之前,CPython需要启用全局符号可见性以供共享库使用。这可以通过调用sys.setdlopenflags(flag_values)来实现。导入lupa模块将自动尝试设置正确的dlopen标志,如果它找到了定义所需标志常量的平台特定DLFCN Python模块。在这种情况下,在Lua中使用二进制模块应该能够正常工作。

如果此设置失败,则必须手动设置标志。在上述配置调用中,参数flag_values必须表示系统RTLD_NEWRTLD_GLOBAL值的总和。如果RTLD_NEW为2,RTLD_GLOBAL为256,则需要调用sys.setdlopenflags(258)

假设Lua的luaposixposix)模块可用,以下命令应在Linux系统上工作

>>> import sys
>>> orig_dlflags = sys.getdlopenflags()
>>> sys.setdlopenflags(258)
>>> import lupa
>>> sys.setdlopenflags(orig_dlflags)

>>> lua = lupa.LuaRuntime()
>>> posix_module = lua.require('posix')     # doctest: +SKIP

使用不同的Lua版本进行构建

构建配置为自动搜索已安装的LuaJIT版本,然后是Lua版本,如果两者都未找到,则使用捆绑的LuaJIT或Lua版本。

如果您希望使用特定版本的Lua构建Lupa,您可以在设置中配置以下选项

选项

描述

--lua-lib <libfile>

Lua库文件路径,例如--lua-lib /usr/local/lib/lualib.a

--lua-includes <incdir>

Lua包含目录,例如--lua-includes /usr/local/include

--use-bundle

使用捆绑的LuaJIT或Lua,而不是搜索已安装的版本。

--no-bundle

不使用捆绑的LuaJIT/Lua,搜索LuaJIT或Lua的已安装版本,例如使用pkg-config

--no-lua-jit

不使用或搜索LuaJIT,只使用或搜索Lua。

安装lupa

使用LuaJIT2构建

  1. 下载并解压lupa

    http://pypi.python.org/pypi/lupa

  2. 下载LuaJIT2

    http://luajit.org/download.html

  3. 将存档解压到lupa基本目录中,例如

    .../lupa-0.1/LuaJIT-2.0.2
  4. 构建LuaJIT

    cd LuaJIT-2.0.2
    make
    cd ..

    如果您需要特定的C编译器标志,请按照以下方式将其传递给make

    make CFLAGS="..."

    对于Windows和MacOS-X等更复杂的目标平台,请参阅LuaJIT的官方安装说明

    注意:在Windows上构建时,请确保除了lua51.dll外,还创建了lua51.lib。MSVC构建生成此文件,MinGW不会。

  5. 构建lupa

    python setup.py build_ext -i

    或您选择的任何其他distutils目标,例如buildbdist目标之一。有关帮助,请参阅distutils文档,以及有关构建扩展模块的提示

    据报道,在64位MacOS-X安装上,需要以下附加编译器标志,因为嵌入了LuaJIT

    -pagezero_size 10000 -image_base 100000000

    您可以在以下有些不清楚的博客文章中找到有关MacOS-X的附加安装提示,该文章可能会也可能不会告诉您在安装过程的哪个点提供这些标志。

    此外,在64位MacOS-X上,您通常需要设置环境变量ARCHFLAGS,以确保它只为您的系统构建,而不是尝试生成同时支持32位和64位的fat二进制文件。

    export ARCHFLAGS="-arch x86_64"

    请注意,这适用于LuaJIT和Lupa,所以如果您最初忘记设置它,请确保尝试重新干净构建所有内容。

使用Lua 5.x构建

使用标准的(非JIT)Lua运行时与Lupa一起使用也行。最简单的方法是使用捆绑的lua子模块。

  1. 克隆子模块

    $ git submodule update --init third-party/lua
  2. 构建Lupa

    $ python3 setup.py bdist_wheel --use-bundle --with-cython

您还可以通过安装Lua 5.x软件包(包括任何开发软件包(头文件等))来构建它。在采用“pkg-config”配置机制的系统上,Lupa的setup.py将自动选择LuaJIT2或Lua,如果有LuaJIT2则会优先选择LuaJIT2。如果您已安装两者但不想使用LuaJIT2,请将--no-luajit选项传递给setup.py脚本。

在其他系统上,您可能需要外部提供构建参数,例如使用环境变量或手动更改setup.py脚本。如果自动找不到LuaJIT2或Lua,请将--no-luajit选项传递给setup.py脚本以忽略失败。

有关更多信息,请阅读此邮件列表帖子

https://www.freelists.org/post/lupa-dev/Lupa-with-normal-Lua-interpreter-Lua-51,2

从软件包中安装lupa

Debian/Ubuntu + Lua 5.2

  1. 安装Lua 5.2开发包

    $ apt-get install liblua5.2-dev
  2. 安装lupa

    $ pip install lupa

Debian/Ubuntu + LuaJIT2

  1. 安装LuaJIT2开发包

    $ apt-get install libluajit-5.1-dev
  2. 安装lupa

    $ pip install lupa

根据操作系统版本,您可能得到较旧的LuaJIT2版本。

OS X + Lua 5.2 + Homebrew

  1. 安装Lua

    $ brew install lua
  2. 安装pkg-config

    $ brew install pkg-config
  3. 安装lupa

    $ pip install lupa

Lupa变更日志

2.2 (2024-06-02)

  • 添加了新的方法LuaRuntime.gccollect(),用于触发Lua垃圾收集器。

  • 添加了新的上下文管理器LuaRuntime.nogc(),用于暂时禁用Lua垃圾收集器。

  • 在运行Lua代码时,从线程中释放Python对象可能会导致死锁。

  • 捆绑的LuaJIT版本已更新到最新的git分支。

  • 使用Cython 3.0.10构建。

2.1 (2024-03-24)

  • GH#199:table_from()方法新增了一个关键字参数recursive=False。如果为真,Python数据结构将被递归映射到Lua表中,通过身份去重处理循环和重复项。

  • GH#248:LuaRuntime的eval、execute和compile方法新增了新的关键字选项mode和name,允许限制输入类型并修改(代码块)名称,类似于Lua load()函数中的类似参数。参见https://lua.ac.cn/manual/5.4/manual.html#pdf-load

  • GH#246:在Lupa 2.0中引入的特定版本Lua模块中,加载Lua模块不起作用。结果证明,在给定的Python运行中只能启用其中一个,因此现在将启用它留给了用户根据需要显式启用。 (原始补丁由Richard Connon提供)

  • GH#234:捆绑的Lua 5.1已更新到5.1.5,Lua 5.2已更新到5.2.4。 (补丁由xxyzz提供)

  • 捆绑的Lua 5.4已更新到5.4.6。

  • 捆绑的LuaJIT版本已更新到最新的git分支。

  • 使用Cython 3.0.9构建,以改进对Python 3.12/13的支持。

2.0 (2023-04-03)

  • GH#217:Python异常消息中的Lua堆栈跟踪现在已反转,以匹配Python堆栈跟踪的顺序。

  • GH#196:Lupa现在提供单独的扩展模块,使用Lua 5.3、Lua 5.4、LuaJIT 2.0和LuaJIT 2.1 beta构建。请注意,这是构建特定的,可能取决于平台。可以使用正常的Python导入级联。

  • GH#211:新的选项max_memory允许限制Lua代码的内存使用量。 (补丁由Leo Developer提供)

  • GH#171:Lua中的Python引用现在更安全地引用计数,以防止垃圾收集问题。 (补丁由Guilherme Dantas提供)

  • GH#146:Lua 5.3+中的Lua整数可以转换为Python整数,并从Python整数转换回来。(由Guilherme Dantas补丁)

  • GH#180:如果Lua支持,python.enumerate()函数现在返回整数索引。(由Guilherme Dantas补丁)

  • GH#178:Lua整数的限制可以通过模块读取为LUA_MAXINTEGERLUA_MININTEGER。(由Guilherme Dantas补丁)

  • GH#174:在Lua中调用__index方法时,如果在Python中进行表索引查找失败,可能会导致Python崩溃。(由Guilherme Dantas补丁)

  • GH#137:将None作为dict键传递给table_from()会导致崩溃。(由Leo Developer补丁)

  • GH#176:添加了一个新函数python.args(*args, **kwargs),以帮助从Lua代码中构建Python参数元组和关键字参数字典,用于Python函数调用。

  • GH#177:在解包非序列表时,将引发IndexError。以前,非序列项被简单地忽略。

  • GH#179:解决有关有符号/无符号比较的一些C编译器警告。(由Guilherme Dantas补丁)

  • 使用Cython 0.29.34构建。

1.14.1 (2022-11-16)

  • 使用Cython 0.29.32重新构建以支持Python 3.11。

1.13 (2022-03-01)

  • 源代码分发中缺少捆绑的Lua源文件。

1.12 (2022-02-24)

  • GH#197:上一版本中的一些二进制轮在绑定Lua时没有正确链接。

  • GH#194:源代码分发的SOURCES.txt元数据中出现绝对文件路径。

1.11 (2022-02-23)

  • 在二进制轮和捆绑的Lua中使用Lua 5.4.4。

  • 使用Cython 0.29.28构建以支持Python 3.10/11。

1.10 (2021-09-02)

  • GH#147:支持Lua 5.4。(由Russel Davis补丁)

  • 通过lupa.LUA_VERSIONLuaRuntime.lua_version提供Lua库的运行时版本作为元组(例如(5,3))。

  • 将Lua实现名称和版本字符串提供为LuaRuntime.lua_implementation

  • setup.py接受新的命令行参数--lua-lib--lua-includes以指定

  • 在二进制轮和捆绑的Lua中使用Lua 5.4.3。

  • 使用Cython 0.29.24构建以支持Python 3.9。

1.9 (2019-12-21)

  • 如果可用,针对Lua 5.3进行构建。

  • 在二进制轮和捆绑的Lua中使用Lua 5.3.5。

  • GH#129:修复Python 3.x中的Lua模块加载问题。

  • GH#126:修复在安装Lua为“lua52”包的Linux系统上的构建问题。

  • 使用Cython 0.29.14构建以提高Py3.8兼容性。

1.8 (2019-02-01)

  • GH#107:修复Py3中已弃用的导入。

  • 使用Cython 0.29.3构建以提高Py3.7兼容性。

1.7 (2018-08-06)

  • GH#103:为MS Windows提供轮,并修复Py2.7上的MSVC构建。

1.6 (2017-12-15)

  • GH#95:提高与Lua 5.3的兼容性。(由TitanSnow补丁)

1.5 (2017-09-16)

  • GH#93:添加新方法LuaRuntime.compile()以编译Lua代码而不执行它。(由TitanSnow补丁)

  • GH#91:将Lua 5.3捆绑在源代码分发中,以简化一次性安装。(由TitanSnow补丁)

  • GH#87:在调试模式下将Lua堆栈跟踪包含在输出中。(由aaiyer补丁)

  • GH#78:允许Lua代码拦截Python异常。(由Sergey Dobrov补丁)

  • 使用Cython 0.26.1构建。

1.4 (2016-12-10)

  • GH#82:Lua协程使用了错误的运行时状态(由Sergey Dobrov补丁)

  • GH#81:将本地提供的Lua DLL复制到Windows上安装的包中(由Gareth Coles补丁)

  • 使用Cython 0.25.2构建

1.3 (2016-04-12)

  • GH#70:eval()execute()接受可选的位置参数(由John Vandenberg补丁)

  • GH#65:如果在没有自动编码的情况下设置LuaRuntime,则从Lua调用Python对象上的str()可能会失败(由Mikhail Korobov修复)

  • GH#63:如果没有自动编码地设置LuaRuntime,属性/关键字名称将无法正确编码(由Mikhail Korobov修复)

  • 使用Cython 0.24构建

1.2 (2015-10-10)

  • 从Lua协程返回的回调错误地将协程状态与全局Lua状态混合(由Mikhail Korobov修复)

  • 可以通过LuaRuntime选项禁用Lua中的python.builtins

  • 使用Cython 0.23.4构建

1.1 (2014-11-21)

  • 新模块函数lupa.lua_type(),它返回包装对象的Lua类型作为字符串,或对于常规Python对象返回None

  • 新辅助方法LuaRuntime.table_from(...),它从一个或多个Python映射和/或序列创建Lua表。

  • lupa.unpacks_lua_tablelupa.unpacks_lua_table_method装饰器,允许使用命名参数从Lua调用Python函数。

  • 修复了在关闭时挂起的问题,因为LuaRuntime由于引用循环而未能释放。

  • Lupa现在与其他创建userdata对象的Lua扩展更好地协同工作。

1.0.1 (2014-10-11)

  • 修复了请求包装Lua协程对象的属性时崩溃的问题。

  • 现在在Lua对象上查找不支持它的属性始终引发AttributeError,而不是根据属性名称有时引发TypeError。

1.0 (2014-09-28)

  • 注意:此版本包括以下列出的重大不兼容更改。据信,它们通过更强烈地遵循Lua侧的Lua风格来简化Python代码与Lua代码之间的交互。

    • 现在将包装的python.none对象传递到Lua中,而不是将None返回值映射到nil,这使得它们在Lua代码中更容易处理。这使得行为更加一致,因为之前在某些地方出现none以及在哪些地方使用nil值是有些任意的。唯一的例外是在迭代期间,Lua中第一个返回的值不能是nil,否则循环会提前终止。为了防止这种情况,迭代器返回的任何None值,或者展开元组的任何第一个项是None,仍然映射到python.none。在相同迭代中返回的任何其他值如果是None,则映射到nil,而不是none。这意味着只需要手动检查第一个参数以检查此特殊情况。对于enumerate()迭代器,计数器永远不会是None,因此后续展开的项目永远不会映射到python.none

    • unpack_returned_tuples=True时,迭代现在还会展开元组值,包括enumerate()迭代,它产生一个计数器和展开值的扁平序列。

    • 当从Lua中以“obj:meth()”的方式调用绑定Python方法时,Lupa现在阻止Python第二次在self参数之前添加,因此Python方法现在被调用为“obj.meth()”。以前,它是作为“obj.meth(obj)”调用的。请注意,当对象本身从Lua显式传递作为第一个参数时,这可能是不希望的,例如,当调用“func(obj)”时,“func”是“obj.meth”,但这些组合应该是罕见的。作为这种情况的解决方案,用户代码可以包装绑定方法在另一个函数中,以便最终调用来自Python。

  • 垃圾收集适用于跨越Python和Lua两个运行时的引用循环。

  • 从Python调用Lua并返回Python时,在内部最深层调用之前没有清理Lua调用参数,导致它们可能泄漏到嵌套的Python调用或其返回参数

  • 支持Lua 5.2(除Lua 5.1和LuaJIT 2.0外)

  • Lua表支持Python的“del”语句来删除项目(由Jason Fried提供的补丁)

  • 通过实现LuaRuntime(attribute_handlers参数)的显式获取器和设置器函数,可以更精细地控制属性查找机制

  • 从Python对Lua对象进行项目赋值/查找时,不再对双下划线名称进行特殊处理(与属性查找不同)

0.21 (2014-02-12)

  • 使用新的Cython功能清理了一些垃圾回收问题

  • 新的LuaRuntime选项unpack_returned_tuples,该选项自动将Python函数返回的元组拆分为单独的Lua对象(而不是返回单个Python元组对象)

  • 从模块API中删除了一些内部包装类

  • Windows构建修复

  • Py3.x构建修复

  • 支持使用Lua 5.1而不是LuaJIT进行构建(setup.py –no-luajit)

  • 从发布源构建时不再默认使用Cython(通过传递--with-cython来明确请求重建)

  • 从未发布源构建时需要Cython 0.20+

  • 使用Cython 0.20.1构建

0.20 (2011-05-22)

  • 修复在Python代码中遍历Lua表时“释放None”崩溃的问题

  • 支持Lua代码对Python对象的属性访问进行过滤

  • 修复:设置Lua代码源编码时出现错误

0.19 (2011-03-06)

  • 修复创建多个LuaRuntime实例时的严重资源泄漏

  • 针对二进制模块导入的可移植性修复

0.18 (2010-11-06)

  • 修复在迭代中返回Py_None对象以代替nil,这会导致迭代终止的问题

  • 在将Python值转换为Lua时,在nil具有特殊意义的地方将None表示为Py_None对象,但在不影响的地方保持为nil

  • 支持在python.enumerate()中设置计数器起始值

  • python.enumerate()的本地实现,速度快了几倍

  • 加快Lua遍历Python对象的速度

0.17 (2010-11-05)

  • 在Lua中添加了新的辅助函数python.enumerate(),该函数返回Python对象的Lua迭代器并添加到每个项目中的0基索引。

  • 在Lua中添加了新的辅助函数python.iterex(),该函数返回Python对象的Lua迭代器并解包迭代器生成的任何元组。

  • 在Lua中添加了新的辅助函数python.iter(),该函数返回Python对象的Lua迭代器。

  • 重新建立了Lua代码中的辅助函数python.as_function(),因为它在Lua无法确定如何运行Python函数的情况下可能需要。

0.16 (2010-09-03)

  • 删除了Lua的辅助函数python.as_function(),因为现在所有Python对象都可以从Lua调用(如果它们不可调用,则可能在调用时引发TypeError

  • 修复了0.13及以后版本中回归的问题,即普通Lua函数由于意外使用了元表而无法打印

  • 修复了对没有元表的包装Lua对象调用str()时崩溃的问题

0.15 (2010-09-02)

  • 支持在支持它的系统上加载二进制Lua模块

0.14 (2010-08-31)

  • 重新许可为LuaJIT2使用的MIT许可证,以简化许可考虑

0.13.1 (2010-08-30)

  • 使用Cython 0.13修复Cython生成的C文件

0.13 (2010-08-29)

  • 修复了当对象的__tostring()元方法失败时在str(lua_object)上出现未定义行为的问题

  • LuaError消息中删除了冗余的“错误:”前缀

  • 从Lua代码中访问Python的python.builtins

  • 基于支持协议(可调用、获取项、获取属性)的更通用的 Python 对象包装规则

  • 新增辅助函数 as_attrgetter()as_itemgetter(),用于指定在 Python 代码中包装 Python 对象时 Lua 索引所使用的 Python 对象协议

  • 新增辅助函数 python.as_attrgetter()python.as_itemgetter()python.as_function(),用于指定在 Lua 代码中对 Python 对象进行 Lua 索引时使用的 Python 对象协议

  • 从 Lua 代码中访问 Python 对象的项和属性

0.12 (2010-08-16)

  • 修复了在迭代表时 Lua 栈泄漏的问题

  • 修复了迭代后丢失 Lua 对象引用的问题

0.11 (2010-08-07)

  • 在 Lua 语法错误发生时,错误报告未能清理栈,导致错误可能泄漏到下一个 Lua 运行中

  • Lua 错误消息未正确解码

0.10 (2010-07-27)

0.9 (2010-07-23)

  • 修复了 LuaObject 实例上的 Python 特殊双下划线方法访问问题

  • 通过专用包装类支持 Lua 协程,包括 Python 迭代支持。在 Python 空间中,Lua 协程的行为与 Python 生成器完全一样。

0.8 (2010-07-21)

  • 支持从 Lua 评估中返回多个值

  • repr() 支持 Lua 对象

  • LuaRuntime.table() 方法,用于从 Python 空间创建 Lua 表

  • 修复了 str(LuaObject) 的编码问题

0.7 (2010-07-18)

  • LuaRuntime.require()LuaRuntime.globals() 方法

  • LuaRuntime.run() 重命名为 LuaRuntime.execute()

  • 支持 len()setattr() 和 Lua 对象的索引

  • LuaRuntime 中提供所有内置 Lua 库,包括库加载支持

  • 修复了线程锁定问题

  • 修复了从 Python 空间将 Lua 对象传回运行时的问题

0.6 (2010-07-18)

  • 为 Lua 对象(例如表)提供 Python 迭代支持

  • 修复了线程问题

  • 修复了编译警告

0.5 (2010-07-14)

  • 为每个 LuaRuntime 实例提供显式的编码选项,以解码/编码字符串和 Lua 代码

0.4 (2010-07-14)

  • 在 Lua 对象上进行属性读取访问,例如从 Python 读取 Lua 表值

  • 在 Lua 对象上使用 str()

  • 在源代码下载中包含 .hg 仓库

  • 将缺失的文件添加到源代码分发中

0.3 (2010-07-13)

  • 修复了多个线程问题

  • 在调用 Lua 时安全释放 GIL

0.2 (2010-07-13)

  • 通过 Lua 调用传播 Python 异常

0.1 (2010-07-12)

  • 首次公开发布

许可协议

Lupa

版权所有(c)2010-2017 Stefan Behnel。保留所有权利。

特此授予任何获得此软件及其相关文档副本(以下简称“软件”)的人士,免费使用该软件的权利,不受任何限制,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或出售软件副本的权利,并允许将软件提供给他人使用,但须遵守以下条件

上述版权声明和本许可声明应包含在软件的所有副本或主要部分中。

本软件按“原样”提供,不提供任何形式的明示或暗示保证,包括但不限于适销性、特定用途的适用性和非侵权性。在任何情况下,作者或版权所有者均不对任何索赔、损害或其他责任负责,无论是基于合同、侵权或其他法律行为,源于、因之或与此软件或其使用或其他交易有关。

Lua

(参见 https://lua.ac.cn/license.html

版权所有 © 1994–2017 Lua.org,PUC-Rio。

特此授予任何获得此软件及其相关文档副本(以下简称“软件”)的人士,免费使用该软件的权利,不受任何限制,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或出售软件副本的权利,并允许将软件提供给他人使用,但须遵守以下条件

上述版权声明和本许可声明应包含在软件的所有副本或主要部分中。

本软件按“原样”提供,不提供任何形式的明示或暗示保证,包括但不限于适销性、特定用途的适用性和非侵权性。在任何情况下,作者或版权所有者均不对任何索赔、损害或其他责任负责,无论是基于合同、侵权或其他法律行为,源于、因之或与此软件或其使用或其他交易有关。

项目详情


下载文件

下载适用于您平台的文件。如果您不确定选择哪一个,请了解有关安装包的更多信息。

源代码分发

lupa-2.2.tar.gz (7.1 MB 查看哈希值)

上传时间: 源代码

构建版本

lupa-2.2-pp310-pypy310_pp73-win_amd64.whl (957.9 kB 查看哈希值)

上传时间: PyPy Windows x86-64

lupa-2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.0 MB 查看哈希值)

上传时间: PyPy manylinux: glibc 2.17+ x86-64

lupa-2.2-pp310-pypy310_pp73-macosx_11_0_x86_64.whl (840.3 kB 查看哈希值)

上传时间: PyPy macOS 11.0+ x86-64

lupa-2.2-pp39-pypy39_pp73-win_amd64.whl (957.6 kB 查看哈希值)

上传时间: PyPy Windows x86-64

lupa-2.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.0 MB 查看哈希值)

上传时间: PyPy manylinux: glibc 2.17+ x86-64

lupa-2.2-pp39-pypy39_pp73-macosx_11_0_x86_64.whl (839.2 kB 查看哈希值)

上传时间: PyPy macOS 11.0+ x86-64

lupa-2.2-pp38-pypy38_pp73-win_amd64.whl (957.4 kB 查看哈希值)

上传于 PyPy Windows x86-64

lupa-2.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.0 MB 查看哈希值)

上传于 PyPy manylinux: glibc 2.17+ x86-64

lupa-2.2-pp38-pypy38_pp73-macosx_11_0_x86_64.whl (840.7 kB 查看哈希值)

上传于 PyPy macOS 11.0+ x86-64

lupa-2.2-pp37-pypy37_pp73-win_amd64.whl (957.2 kB 查看哈希值)

上传于 PyPy Windows x86-64

lupa-2.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.0 MB 查看哈希值)

上传于 PyPy manylinux: glibc 2.17+ x86-64

lupa-2.2-pp37-pypy37_pp73-macosx_11_0_x86_64.whl (840.5 kB 查看哈希值)

上传于 PyPy macOS 11.0+ x86-64

lupa-2.2-cp312-cp312-win_amd64.whl (1.0 MB 查看哈希值)

上传于 CPython 3.12 Windows x86-64

lupa-2.2-cp312-cp312-win32.whl (827.2 kB 查看哈希值)

上传于 CPython 3.12 Windows x86

lupa-2.2-cp312-cp312-musllinux_1_1_x86_64.whl (2.1 MB 查看哈希值)

上传于 CPython 3.12 musllinux: musl 1.1+ x86-64

lupa-2.2-cp312-cp312-musllinux_1_1_i686.whl (1.2 MB 查看哈希值)

上传于 CPython 3.12 musllinux: musl 1.1+ i686

lupa-2.2-cp312-cp312-musllinux_1_1_aarch64.whl (1.1 MB 查看哈希值)

上传于 CPython 3.12 musllinux: musl 1.1+ ARM64

lupa-2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.0 MB 查看哈希值)

上传于 CPython 3.12 manylinux: glibc 2.17+ x86_64

lupa-2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.0 MB 查看哈希值)

上传于 CPython 3.12 manylinux: glibc 2.17+ ARM64

lupa-2.2-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl (1.1 MB 查看哈希值)

上传于 CPython 3.12 manylinux: glibc 2.12+ i686 manylinux: glibc 2.17+ i686

lupa-2.2-cp312-cp312-macosx_11_0_x86_64.whl (998.2 kB 查看哈希值)

上传于 CPython 3.12 macOS 11.0+ x86_64

lupa-2.2-cp312-cp312-macosx_11_0_universal2.whl (1.9 MB 查看哈希值)

上传于 CPython 3.12 macOS 11.0+ universal2 (ARM64, x86_64)

lupa-2.2-cp312-cp312-macosx_11_0_arm64.whl (933.6 kB 查看哈希值)

上传于 CPython 3.12 macOS 11.0+ ARM64

lupa-2.2-cp311-cp311-win_amd64.whl (1.0 MB 查看哈希值)

上传于 CPython 3.11 Windows x86_64

lupa-2.2-cp311-cp311-win32.whl (819.6 kB 查看哈希值)

上传于 CPython 3.11 Windows x86

lupa-2.2-cp311-cp311-musllinux_1_1_x86_64.whl (2.1 MB 查看哈希值)

上传于 CPython 3.11 musllinux: musl 1.1+ x86_64

lupa-2.2-cp311-cp311-musllinux_1_1_i686.whl (1.2 MB 查看哈希值)

上传于 CPython 3.11 musllinux: musl 1.1+ i686

lupa-2.2-cp311-cp311-musllinux_1_1_aarch64.whl (1.1 MB 查看哈希值)

上传于 CPython 3.11 musllinux: musl 1.1+ ARM64

lupa-2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB 查看哈希值)

上传于 CPython 3.11 manylinux: glibc 2.17+ x86-64

lupa-2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.1 MB 查看哈希值)

上传于 CPython 3.11 manylinux: glibc 2.17+ ARM64

lupa-2.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl (1.2 MB 查看哈希值)

上传于 CPython 3.11 manylinux: glibc 2.12+ i686 manylinux: glibc 2.17+ i686

lupa-2.2-cp311-cp311-macosx_11_0_x86_64.whl (987.4 kB 查看哈希值)

上传于 CPython 3.11 macOS 11.0+ x86-64

lupa-2.2-cp311-cp311-macosx_11_0_universal2.whl (1.9 MB 查看哈希值)

上传于 CPython 3.11 macOS 11.0+ universal2 (ARM64, x86-64)

lupa-2.2-cp311-cp311-macosx_11_0_arm64.whl (929.7 kB 查看哈希值)

上传于 CPython 3.11 macOS 11.0+ ARM64

lupa-2.2-cp310-cp310-win_amd64.whl (992.9 kB 查看哈希值)

上传于 CPython 3.10 Windows x86-64

lupa-2.2-cp310-cp310-win32.whl (819.5 kB 查看哈希值)

上传于 CPython 3.10 Windows x86

lupa-2.2-cp310-cp310-musllinux_1_1_x86_64.whl (2.1 MB 查看哈希值)

上传于 CPython 3.10 musllinux: musl 1.1+ x86-64

lupa-2.2-cp310-cp310-musllinux_1_1_i686.whl (1.2 MB 查看哈希值)

上传于 CPython 3.10 musllinux: musl 1.1+ i686

lupa-2.2-cp310-cp310-musllinux_1_1_aarch64.whl (1.1 MB 查看哈希值)

上传时间 CPython 3.10 musllinux: musl 1.1+ ARM64

lupa-2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB 查看哈希值)

上传时间 CPython 3.10 manylinux: glibc 2.17+ x86-64

lupa-2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.1 MB 查看哈希值)

上传时间 CPython 3.10 manylinux: glibc 2.17+ ARM64

lupa-2.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl (1.2 MB 查看哈希值)

上传时间 CPython 3.10 manylinux: glibc 2.12+ i686 manylinux: glibc 2.17+ i686

lupa-2.2-cp310-cp310-macosx_11_0_x86_64.whl (983.2 kB 查看哈希值)

上传时间 CPython 3.10 macOS 11.0+ x86-64

lupa-2.2-cp310-cp310-macosx_11_0_universal2.whl (1.9 MB 查看哈希值)

上传时间 CPython 3.10 macOS 11.0+ universal2 (ARM64, x86-64)

lupa-2.2-cp310-cp310-macosx_11_0_arm64.whl (925.8 kB 查看哈希值)

上传时间 CPython 3.10 macOS 11.0+ ARM64

lupa-2.2-cp39-cp39-win_amd64.whl (993.8 kB 查看哈希值)

上传时间 CPython 3.9 Windows x86-64

lupa-2.2-cp39-cp39-win32.whl (820.2 kB 查看哈希值)

上传时间 CPython 3.9 Windows x86

lupa-2.2-cp39-cp39-musllinux_1_1_x86_64.whl (2.1 MB 查看哈希值)

上传时间 CPython 3.9 musllinux: musl 1.1+ x86-64

lupa-2.2-cp39-cp39-musllinux_1_1_i686.whl (1.2 MB 查看哈希值)

上传时间 CPython 3.9 musllinux: musl 1.1+ i686

lupa-2.2-cp39-cp39-musllinux_1_1_aarch64.whl (1.1 MB 查看哈希值)

上传时间 CPython 3.9 musllinux: musl 1.1+ ARM64

lupa-2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB 查看哈希值)

上传时间 CPython 3.9 manylinux: glibc 2.17+ x86-64

lupa-2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.1 MB 查看哈希值)

上传时间 CPython 3.9 manylinux: glibc 2.17+ ARM64

lupa-2.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl (1.2 MB 查看哈希值)

上传时间 CPython 3.9 manylinux: glibc 2.12+ i686 manylinux: glibc 2.17+ i686

lupa-2.2-cp39-cp39-macosx_11_0_x86_64.whl (984.0 kB 查看哈希值)

上传时间 CPython 3.9 macOS 11.0+ x86-64

lupa-2.2-cp39-cp39-macosx_11_0_universal2.whl (1.9 MB 查看哈希值)

上传时间 CPython 3.9 macOS 11.0+ universal2 (ARM64, x86-64)

lupa-2.2-cp39-cp39-macosx_11_0_arm64.whl (926.6 kB 查看哈希值)

上传时间 CPython 3.9 macOS 11.0+ ARM64

lupa-2.2-cp38-cp38-win_amd64.whl (994.6 kB 查看哈希值)

上传时间 CPython 3.8 Windows x86-64

lupa-2.2-cp38-cp38-win32.whl (820.7 kB 查看哈希值)

上传时间 CPython 3.8 Windows x86

lupa-2.2-cp38-cp38-musllinux_1_1_x86_64.whl (2.2 MB 查看哈希值)

上传于 CPython 3.8 musllinux: musl 1.1+ x86-64

lupa-2.2-cp38-cp38-musllinux_1_1_i686.whl (1.2 MB 查看哈希值)

上传于 CPython 3.8 musllinux: musl 1.1+ i686

lupa-2.2-cp38-cp38-musllinux_1_1_aarch64.whl (1.1 MB 查看哈希值)

上传于 CPython 3.8 musllinux: musl 1.1+ ARM64

lupa-2.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB 查看哈希值)

上传于 CPython 3.8 manylinux: glibc 2.17+ x86-64

lupa-2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.1 MB 查看哈希值)

上传于 CPython 3.8 manylinux: glibc 2.17+ ARM64

lupa-2.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl (1.2 MB 查看哈希值)

上传于 CPython 3.8 manylinux: glibc 2.12+ i686 manylinux: glibc 2.17+ i686

lupa-2.2-cp38-cp38-macosx_11_0_x86_64.whl (981.2 kB 查看哈希值)

上传于 CPython 3.8 macOS 11.0+ x86-64

lupa-2.2-cp38-cp38-macosx_11_0_arm64.whl (925.5 kB 查看哈希值)

上传于 CPython 3.8 macOS 11.0+ ARM64

lupa-2.2-cp37-cp37m-win_amd64.whl (985.2 kB 查看哈希值)

上传于 CPython 3.7m Windows x86-64

lupa-2.2-cp37-cp37m-win32.whl (813.0 kB 查看哈希值)

上传于 CPython 3.7m Windows x86

lupa-2.2-cp37-cp37m-musllinux_1_1_x86_64.whl (2.1 MB 查看哈希值)

上传于 CPython 3.7m musllinux: musl 1.1+ x86-64

lupa-2.2-cp37-cp37m-musllinux_1_1_i686.whl (1.2 MB 查看哈希值)

上传于 CPython 3.7m musllinux: musl 1.1+ i686

lupa-2.2-cp37-cp37m-musllinux_1_1_aarch64.whl (1.1 MB 查看哈希值)

上传于 CPython 3.7m musllinux: musl 1.1+ ARM64

lupa-2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.0 MB 查看哈希值)

上传于 CPython 3.7m manylinux: glibc 2.17+ x86-64

lupa-2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.0 MB 查看哈希值)

上传于 CPython 3.7m manylinux: glibc 2.17+ ARM64

lupa-2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl (1.1 MB 查看哈希值)

上传于 CPython 3.7m manylinux: glibc 2.12+ i686 manylinux: glibc 2.17+ i686

lupa-2.2-cp37-cp37m-macosx_11_0_x86_64.whl (970.3 kB 查看哈希值)

上传于 CPython 3.7m macOS 11.0+ x86-64

lupa-2.2-cp36-cp36m-win_amd64.whl (1.1 MB 查看哈希值)

上传于 CPython 3.6m Windows x86-64

lupa-2.2-cp36-cp36m-win32.whl (911.1 kB 查看哈希值)

上传于 CPython 3.6m Windows x86

lupa-2.2-cp36-cp36m-musllinux_1_1_x86_64.whl (2.1 MB 查看哈希值)

上传于 CPython 3.6m musllinux: musl 1.1+ x86-64

lupa-2.2-cp36-cp36m-musllinux_1_1_i686.whl (1.1 MB 查看哈希值)

上传于 CPython 3.6m musllinux: musl 1.1+ i686

lupa-2.2-cp36-cp36m-musllinux_1_1_aarch64.whl (1.1 MB 查看哈希值)

上传于 CPython 3.6m musllinux: musl 1.1+ ARM64

lupa-2.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.0 MB 查看哈希值)

上传于 CPython 3.6m manylinux: glibc 2.17+ x86-64

lupa-2.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.0 MB 查看哈希值)

上传于 CPython 3.6m manylinux: glibc 2.17+ ARM64

lupa-2.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl (1.1 MB 查看哈希值)

上传于 CPython 3.6m manylinux: glibc 2.12+ i686 manylinux: glibc 2.17+ i686

lupa-2.2-cp36-cp36m-macosx_11_0_x86_64.whl (1.0 MB 查看哈希值)

上传于 CPython 3.6m macOS 11.0+ x86-64

lupa-2.2-cp27-cp27m-macosx_11_0_x86_64.whl (1.0 MB 查看哈希值)

上传于 CPython 2.7m macOS 11.0+ x86-64