{"id":13502127,"url":"https://github.com/scoder/lupa","last_synced_at":"2025-05-16T07:06:28.270Z","repository":{"id":645750,"uuid":"1347652","full_name":"scoder/lupa","owner":"scoder","description":"Lua in Python","archived":false,"fork":false,"pushed_at":"2025-04-26T09:50:30.000Z","size":956,"stargazers_count":1067,"open_issues_count":63,"forks_count":142,"subscribers_count":30,"default_branch":"master","last_synced_at":"2025-04-26T10:32:51.366Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://pypi.python.org/pypi/lupa","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/scoder.png","metadata":{"files":{"readme":"README.rst","changelog":"CHANGES.rst","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2011-02-09T19:16:15.000Z","updated_at":"2025-04-26T09:50:35.000Z","dependencies_parsed_at":"2023-07-06T14:31:27.526Z","dependency_job_id":"9b7ae969-6e42-4144-aa9d-d7e51dc69230","html_url":"https://github.com/scoder/lupa","commit_stats":{"total_commits":623,"total_committers":24,"mean_commits":"25.958333333333332","dds":0.449438202247191,"last_synced_commit":"67ba19406d6587139fc2779f549e9543da68ff11"},"previous_names":[],"tags_count":43,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scoder%2Flupa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scoder%2Flupa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scoder%2Flupa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scoder%2Flupa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scoder","download_url":"https://codeload.github.com/scoder/lupa/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254485063,"owners_count":22078767,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T22:02:02.831Z","updated_at":"2025-05-16T07:06:28.204Z","avatar_url":"https://github.com/scoder.png","language":"Python","funding_links":[],"categories":["Python","资源","Resources"],"sub_categories":["实现, 解释器, 和绑定","Implementations, Interpreters, and Bindings"],"readme":"Lupa\n====\n\n.. image:: logo/logo-220x200.png\n\nLupa integrates the runtimes of Lua_ or LuaJIT2_ into CPython.\nIt is a partial rewrite of LunaticPython_ in Cython_ with some\nadditional features such as proper coroutine support.\n\n.. _Lua: http://lua.org/\n.. _LuaJIT2: http://luajit.org/\n.. _LunaticPython: http://labix.org/lunatic-python\n.. _Cython: http://cython.org\n\nFor questions not answered here, please contact the `Lupa mailing list`_.\n\n.. _`Lupa mailing list`: http://www.freelists.org/list/lupa-dev\n\n.. contents:: :local:\n\n\nMajor features\n--------------\n\n* separate Lua runtime states through a ``LuaRuntime`` class\n\n* Python coroutine wrapper for Lua coroutines\n\n* iteration support for Python objects in Lua and Lua objects in\n  Python\n\n* proper encoding and decoding of strings (configurable per runtime,\n  UTF-8 by default)\n\n* frees the GIL and supports threading in separate runtimes when\n  calling into Lua\n\n* tested with Python 2.7/3.6 and later\n\n* ships with Lua 5.1, 5.2, 5.3 and 5.4\n  as well as LuaJIT 2.0 and 2.1 on systems that support it.\n\n* easy to hack on and extend as it is written in Cython, not C\n\n\nWhy the name?\n-------------\n\nIn Latin, \"lupa\" is a female wolf, as elegant and wild as it sounds.\nIf you don't like this kind of straight forward allegory to an\nendangered species, you may also happily assume it's just an\namalgamation of the phonetic sounds that start the words \"Lua\" and\n\"Python\", two from each to keep the balance.\n\n\nWhy use it?\n-----------\n\nIt complements Python very well.  Lua is a language as dynamic as\nPython, but LuaJIT compiles it to very fast machine code, sometimes\nfaster than many statically compiled languages for computational code.\nThe language runtime is very small and carefully designed for\nembedding.  The complete binary module of Lupa, including a statically\nlinked LuaJIT2 runtime, only weighs some 800KB on a 64 bit machine.\nWith standard Lua 5.2, it's less than 600KB.\n\nHowever, the Lua ecosystem lacks many of the batteries that Python\nreadily includes, either directly in its standard library or as third\nparty packages. This makes real-world Lua applications harder to write\nthan equivalent Python applications. Lua is therefore not commonly\nused as primary language for large applications, but it makes for a\nfast, high-level and resource-friendly backup language inside of\nPython when raw speed is required and the edit-compile-run cycle of\nbinary extension modules is too heavy and too static for agile\ndevelopment or hot-deployment.\n\nLupa is a very fast and thin wrapper around Lua or LuaJIT.  It makes it\neasy to write dynamic Lua code that accompanies dynamic Python code by\nswitching between the two languages at runtime, based on the tradeoff\nbetween simplicity and speed.\n\n\nWhich Lua version?\n------------------\n\nThe binary wheels include different Lua versions as well as LuaJIT, if supported.\nBy default, ``import lupa`` uses the latest Lua version, but you can choose\na specific one via import:\n\n.. code:: python\n\n    try:\n        import lupa.luajit21 as lupa\n    except ImportError:\n        try:\n            import lupa.lua54 as lupa\n        except ImportError:\n            try:\n                import lupa.lua53 as lupa\n            except ImportError:\n                import lupa\n\n    print(f\"Using {lupa.LuaRuntime().lua_implementation} (compiled with {lupa.LUA_VERSION})\")\n\n\nExamples\n--------\n\n..\n      \u003e\u003e\u003e import lupa.lua54 as lupa\n\n      ## doctest helpers:\n      \u003e\u003e\u003e try: _ = sorted\n      ... except NameError:\n      ...     def sorted(seq):\n      ...         l = list(seq)\n      ...         l.sort()\n      ...         return l\n\n.. code:: python\n\n      \u003e\u003e\u003e from lupa.lua54 import LuaRuntime\n      \u003e\u003e\u003e lua = LuaRuntime(unpack_returned_tuples=True)\n\n      \u003e\u003e\u003e lua.eval('1+1')\n      2\n\n      \u003e\u003e\u003e lua_func = lua.eval('function(f, n) return f(n) end')\n\n      \u003e\u003e\u003e def py_add1(n): return n+1\n      \u003e\u003e\u003e lua_func(py_add1, 2)\n      3\n\n      \u003e\u003e\u003e lua.eval('python.eval(\" 2 ** 2 \")') == 4\n      True\n      \u003e\u003e\u003e lua.eval('python.builtins.str(4)') == '4'\n      True\n\nThe function ``lua_type(obj)`` can be used to find out the type of a\nwrapped Lua object in Python code, as provided by Lua's ``type()``\nfunction:\n\n.. code:: python\n\n      \u003e\u003e\u003e lupa.lua_type(lua_func)\n      'function'\n      \u003e\u003e\u003e lupa.lua_type(lua.eval('{}'))\n      'table'\n\nTo help in distinguishing between wrapped Lua objects and normal\nPython objects, it returns ``None`` for the latter:\n\n.. code:: python\n\n      \u003e\u003e\u003e lupa.lua_type(123) is None\n      True\n      \u003e\u003e\u003e lupa.lua_type('abc') is None\n      True\n      \u003e\u003e\u003e lupa.lua_type({}) is None\n      True\n\nNote the flag ``unpack_returned_tuples=True`` that is passed to create\nthe Lua runtime.  It is new in Lupa 0.21 and changes the behaviour of\ntuples that get returned by Python functions.  With this flag, they\nexplode into separate Lua values:\n\n.. code:: python\n\n      \u003e\u003e\u003e lua.execute('a,b,c = python.eval(\"(1,2)\")')\n      \u003e\u003e\u003e g = lua.globals()\n      \u003e\u003e\u003e g.a\n      1\n      \u003e\u003e\u003e g.b\n      2\n      \u003e\u003e\u003e g.c is None\n      True\n\nWhen set to False, functions that return a tuple pass it through to the\nLua code:\n\n.. code:: python\n\n      \u003e\u003e\u003e non_explode_lua = lupa.LuaRuntime(unpack_returned_tuples=False)\n      \u003e\u003e\u003e non_explode_lua.execute('a,b,c = python.eval(\"(1,2)\")')\n      \u003e\u003e\u003e g = non_explode_lua.globals()\n      \u003e\u003e\u003e g.a\n      (1, 2)\n      \u003e\u003e\u003e g.b is None\n      True\n      \u003e\u003e\u003e g.c is None\n      True\n\nSince the default behaviour (to not explode tuples) might change in a\nlater version of Lupa, it is best to always pass this flag explicitly.\n\n\nPython objects in Lua\n---------------------\n\nPython objects are either converted when passed into Lua (e.g.\nnumbers and strings) or passed as wrapped object references.\n\n.. code:: python\n\n      \u003e\u003e\u003e wrapped_type = lua.globals().type     # Lua's own type() function\n      \u003e\u003e\u003e wrapped_type(1) == 'number'\n      True\n      \u003e\u003e\u003e wrapped_type('abc') == 'string'\n      True\n\nWrapped Lua objects get unwrapped when they are passed back into Lua,\nand arbitrary Python objects get wrapped in different ways:\n\n.. code:: python\n\n      \u003e\u003e\u003e wrapped_type(wrapped_type) == 'function'  # unwrapped Lua function\n      True\n      \u003e\u003e\u003e wrapped_type(len) == 'userdata'       # wrapped Python function\n      True\n      \u003e\u003e\u003e wrapped_type([]) == 'userdata'        # wrapped Python object\n      True\n\nLua supports two main protocols on objects: calling and indexing.  It\ndoes not distinguish between attribute access and item access like\nPython does, so the Lua operations ``obj[x]`` and ``obj.x`` both map\nto indexing.  To decide which Python protocol to use for Lua wrapped\nobjects, Lupa employs a simple heuristic.\n\nPratically all Python objects allow attribute access, so if the object\nalso has a ``__getitem__`` method, it is preferred when turning it\ninto an indexable Lua object.  Otherwise, it becomes a simple object\nthat uses attribute access for indexing from inside Lua.\n\nObviously, this heuristic will fail to provide the required behaviour\nin many cases, e.g. when attribute access is required to an object\nthat happens to support item access.  To be explicit about the\nprotocol that should be used, Lupa provides the helper functions\n``as_attrgetter()`` and ``as_itemgetter()`` that restrict the view on\nan object to a certain protocol, both from Python and from inside\nLua:\n\n.. code:: python\n\n      \u003e\u003e\u003e lua_func = lua.eval('function(obj) return obj[\"get\"] end')\n      \u003e\u003e\u003e d = {'get' : 'value'}\n\n      \u003e\u003e\u003e value = lua_func(d)\n      \u003e\u003e\u003e value == d['get'] == 'value'\n      True\n\n      \u003e\u003e\u003e value = lua_func( lupa.as_itemgetter(d) )\n      \u003e\u003e\u003e value == d['get'] == 'value'\n      True\n\n      \u003e\u003e\u003e dict_get = lua_func( lupa.as_attrgetter(d) )\n      \u003e\u003e\u003e dict_get == d.get\n      True\n      \u003e\u003e\u003e dict_get('get') == d.get('get') == 'value'\n      True\n\n      \u003e\u003e\u003e lua_func = lua.eval(\n      ...     'function(obj) return python.as_attrgetter(obj)[\"get\"] end')\n      \u003e\u003e\u003e dict_get = lua_func(d)\n      \u003e\u003e\u003e dict_get('get') == d.get('get') == 'value'\n      True\n\nNote that unlike Lua function objects, callable Python objects support\nindexing in Lua:\n\n.. code:: python\n\n      \u003e\u003e\u003e def py_func(): pass\n      \u003e\u003e\u003e py_func.ATTR = 2\n\n      \u003e\u003e\u003e lua_func = lua.eval('function(obj) return obj.ATTR end')\n      \u003e\u003e\u003e lua_func(py_func)\n      2\n      \u003e\u003e\u003e lua_func = lua.eval(\n      ...     'function(obj) return python.as_attrgetter(obj).ATTR end')\n      \u003e\u003e\u003e lua_func(py_func)\n      2\n      \u003e\u003e\u003e lua_func = lua.eval(\n      ...     'function(obj) return python.as_attrgetter(obj)[\"ATTR\"] end')\n      \u003e\u003e\u003e lua_func(py_func)\n      2\n\n\nIteration in Lua\n----------------\n\nIteration over Python objects from Lua's for-loop is fully supported.\nHowever, Python iterables need to be converted using one of the\nutility functions which are described here.  This is similar to the\nfunctions like ``pairs()`` in Lua.\n\nTo iterate over a plain Python iterable, use the ``python.iter()``\nfunction.  For example, you can manually copy a Python list into a Lua\ntable like this:\n\n.. code:: python\n\n      \u003e\u003e\u003e lua_copy = lua.eval('''\n      ...     function(L)\n      ...         local t, i = {}, 1\n      ...         for item in python.iter(L) do\n      ...             t[i] = item\n      ...             i = i + 1\n      ...         end\n      ...         return t\n      ...     end\n      ... ''')\n\n      \u003e\u003e\u003e table = lua_copy([1,2,3,4])\n      \u003e\u003e\u003e len(table)\n      4\n      \u003e\u003e\u003e table[1]   # Lua indexing\n      1\n\nPython's ``enumerate()`` function is also supported, so the above\ncould be simplified to:\n\n.. code:: python\n\n      \u003e\u003e\u003e lua_copy = lua.eval('''\n      ...     function(L)\n      ...         local t = {}\n      ...         for index, item in python.enumerate(L) do\n      ...             t[ index+1 ] = item\n      ...         end\n      ...         return t\n      ...     end\n      ... ''')\n\n      \u003e\u003e\u003e table = lua_copy([1,2,3,4])\n      \u003e\u003e\u003e len(table)\n      4\n      \u003e\u003e\u003e table[1]   # Lua indexing\n      1\n\nFor iterators that return tuples, such as ``dict.iteritems()``, it is\nconvenient to use the special ``python.iterex()`` function that\nautomatically explodes the tuple items into separate Lua arguments:\n\n.. code:: python\n\n      \u003e\u003e\u003e lua_copy = lua.eval('''\n      ...     function(d)\n      ...         local t = {}\n      ...         for key, value in python.iterex(d.items()) do\n      ...             t[key] = value\n      ...         end\n      ...         return t\n      ...     end\n      ... ''')\n\n      \u003e\u003e\u003e d = dict(a=1, b=2, c=3)\n      \u003e\u003e\u003e table = lua_copy( lupa.as_attrgetter(d) )\n      \u003e\u003e\u003e table['b']\n      2\n\nNote that accessing the ``d.items`` method from Lua requires passing\nthe dict as ``attrgetter``.  Otherwise, attribute access in Lua would\nuse the ``getitem`` protocol of Python dicts and look up ``d['items']``\ninstead.\n\n\nNone vs. nil\n------------\n\nWhile ``None`` in Python and ``nil`` in Lua differ in their semantics, they\nusually just mean the same thing: no value.  Lupa therefore tries to map one\ndirectly to the other whenever possible:\n\n.. code:: python\n\n      \u003e\u003e\u003e lua.eval('nil') is None\n      True\n      \u003e\u003e\u003e is_nil = lua.eval('function(x) return x == nil end')\n      \u003e\u003e\u003e is_nil(None)\n      True\n\nThe only place where this cannot work is during iteration, because Lua\nconsiders a ``nil`` value the termination marker of iterators.  Therefore,\nLupa special cases ``None`` values here and replaces them by a constant\n``python.none`` instead of returning ``nil``:\n\n.. code:: python\n\n      \u003e\u003e\u003e _ = lua.require(\"table\")\n      \u003e\u003e\u003e func = lua.eval('''\n      ...     function(items)\n      ...         local t = {}\n      ...         for value in python.iter(items) do\n      ...             table.insert(t, value == python.none)\n      ...         end\n      ...         return t\n      ...     end\n      ... ''')\n\n      \u003e\u003e\u003e items = [1, None ,2]\n      \u003e\u003e\u003e list(func(items).values())\n      [False, True, False]\n\nLupa avoids this value escaping whenever it's obviously not necessary.\nThus, when unpacking tuples during iteration, only the first value will\nbe subject to ``python.none`` replacement, as Lua does not look at the\nother items for loop termination anymore.  And on ``enumerate()``\niteration, the first value is known to be always a number and never None,\nso no replacement is needed.\n\n.. code:: python\n\n      \u003e\u003e\u003e func = lua.eval('''\n      ...     function(items)\n      ...         for a, b, c, d in python.iterex(items) do\n      ...             return {a == python.none, a == nil,   --\u003e  a == python.none\n      ...                     b == python.none, b == nil,   --\u003e  b == nil\n      ...                     c == python.none, c == nil,   --\u003e  c == nil\n      ...                     d == python.none, d == nil}   --\u003e  d == nil ...\n      ...         end\n      ...     end\n      ... ''')\n\n      \u003e\u003e\u003e items = [(None, None, None, None)]\n      \u003e\u003e\u003e list(func(items).values())\n      [True, False, False, True, False, True, False, True]\n\n      \u003e\u003e\u003e items = [(None, None)]   # note: no values for c/d =\u003e nil in Lua\n      \u003e\u003e\u003e list(func(items).values())\n      [True, False, False, True, False, True, False, True]\n\n\nNote that this behaviour changed in Lupa 1.0.  Previously, the ``python.none``\nreplacement was done in more places, which made it not always very predictable.\n\n\nLua Tables\n----------\n\nLua tables mimic Python's mapping protocol.  For the special case of\narray tables, Lua automatically inserts integer indices as keys into\nthe table.  Therefore, indexing starts from 1 as in Lua instead of 0\nas in Python.  For the same reason, negative indexing does not work.\nIt is best to think of Lua tables as mappings rather than arrays, even\nfor plain array tables.\n\n.. code:: python\n\n      \u003e\u003e\u003e table = lua.eval('{10,20,30,40}')\n      \u003e\u003e\u003e table[1]\n      10\n      \u003e\u003e\u003e table[4]\n      40\n      \u003e\u003e\u003e list(table)\n      [1, 2, 3, 4]\n      \u003e\u003e\u003e dict(table)\n      {1: 10, 2: 20, 3: 30, 4: 40}\n      \u003e\u003e\u003e list(table.values())\n      [10, 20, 30, 40]\n      \u003e\u003e\u003e len(table)\n      4\n\n      \u003e\u003e\u003e mapping = lua.eval('{ [1] = -1 }')\n      \u003e\u003e\u003e list(mapping)\n      [1]\n\n      \u003e\u003e\u003e mapping = lua.eval('{ [20] = -20; [3] = -3 }')\n      \u003e\u003e\u003e mapping[20]\n      -20\n      \u003e\u003e\u003e mapping[3]\n      -3\n      \u003e\u003e\u003e sorted(mapping.values())\n      [-20, -3]\n      \u003e\u003e\u003e sorted(mapping.items())\n      [(3, -3), (20, -20)]\n\n      \u003e\u003e\u003e mapping[-3] = 3     # -3 used as key, not index!\n      \u003e\u003e\u003e mapping[-3]\n      3\n      \u003e\u003e\u003e sorted(mapping)\n      [-3, 3, 20]\n      \u003e\u003e\u003e sorted(mapping.items())\n      [(-3, 3), (3, -3), (20, -20)]\n\nTo simplify the table creation from Python, the ``LuaRuntime`` comes with\na helper method that creates a Lua table from Python arguments:\n\n.. code:: python\n\n      \u003e\u003e\u003e t = lua.table(10, 20, 30, 40)\n      \u003e\u003e\u003e lupa.lua_type(t)\n      'table'\n      \u003e\u003e\u003e list(t)\n      [1, 2, 3, 4]\n      \u003e\u003e\u003e list(t.values())\n      [10, 20, 30, 40]\n\n      \u003e\u003e\u003e t = lua.table(10, 20, 30, 40, a=1, b=2)\n      \u003e\u003e\u003e t[3]\n      30\n      \u003e\u003e\u003e t['b']\n      2\n\nA second helper method, ``.table_from()``, was added in Lupa 1.1 and accepts\nany number of mappings and sequences/iterables as arguments.  It collects\nall values and key-value pairs and builds a single Lua table from them.\nAny keys that appear in multiple mappings get overwritten with their last\nvalue (going from left to right).\n\n.. code:: python\n\n      \u003e\u003e\u003e t = lua.table_from([10, 20, 30], {'a': 11, 'b': 22}, (40, 50), {'b': 42})\n      \u003e\u003e\u003e t['a']\n      11\n      \u003e\u003e\u003e t['b']\n      42\n      \u003e\u003e\u003e t[5]\n      50\n      \u003e\u003e\u003e sorted(t.values())\n      [10, 11, 20, 30, 40, 42, 50]\n\nSince Lupa 2.1, passing ``recursive=True`` will map data structures recursively\nto Lua tables.\n\n.. code:: python\n\n      \u003e\u003e\u003e t = lua.table_from(\n      ...     [\n      ...         # t1:\n      ...         [\n      ...             [10, 20, 30],\n      ...             {'a': 11, 'b': 22}\n      ...         ],\n      ...         # t2:\n      ...         [\n      ...             (40, 50),\n      ...             {'b': 42}\n      ...         ]\n      ...     ],\n      ...     recursive=True\n      ... )\n      \u003e\u003e\u003e t1, t2 = t.values()\n      \u003e\u003e\u003e list(t1[1].values())\n      [10, 20, 30]\n      \u003e\u003e\u003e t1[2]['a']\n      11\n      \u003e\u003e\u003e t1[2]['b']\n      22\n      \u003e\u003e\u003e t2[2]['b']\n      42\n      \u003e\u003e\u003e list(t1[1].values())\n      [10, 20, 30]\n      \u003e\u003e\u003e list(t2[1].values())\n      [40, 50]\n\nA lookup of non-existing keys or indices returns None (actually ``nil``\ninside of Lua).  A lookup is therefore more similar to the ``.get()``\nmethod of Python dicts than to a mapping lookup in Python.\n\n.. code:: python\n\n      \u003e\u003e\u003e table = lua.table(10, 20, 30, 40)\n      \u003e\u003e\u003e table[1000000] is None\n      True\n      \u003e\u003e\u003e table['no such key'] is None\n      True\n\n      \u003e\u003e\u003e mapping = lua.eval('{ [20] = -20; [3] = -3 }')\n      \u003e\u003e\u003e mapping['no such key'] is None\n      True\n\nNote that ``len()`` does the right thing for array tables but does not\nwork on mappings:\n\n.. code:: python\n\n      \u003e\u003e\u003e len(table)\n      4\n      \u003e\u003e\u003e len(mapping)\n      0\n\nThis is because ``len()`` is based on the ``#`` (length) operator in\nLua and because of the way Lua defines the length of a table.\nRemember that unset table indices always return ``nil``, including\nindices outside of the table size.  Thus, Lua basically looks for an\nindex that returns ``nil`` and returns the index before that.  This\nworks well for array tables that do not contain ``nil`` values, gives\nbarely predictable results for tables with 'holes' and does not work\nat all for mapping tables.  For tables with both sequential and\nmapping content, this ignores the mapping part completely.\n\nNote that it is best not to rely on the behaviour of len() for\nmappings.  It might change in a later version of Lupa.\n\nSimilar to the table interface provided by Lua, Lupa also supports\nattribute access to table members:\n\n.. code:: python\n\n      \u003e\u003e\u003e table = lua.eval('{ a=1, b=2 }')\n      \u003e\u003e\u003e table.a, table.b\n      (1, 2)\n      \u003e\u003e\u003e table.a == table['a']\n      True\n\nThis enables access to Lua 'methods' that are associated with a table,\nas used by the standard library modules:\n\n.. code:: python\n\n      \u003e\u003e\u003e string = lua.eval('string')    # get the 'string' library table\n      \u003e\u003e\u003e print( string.lower('A') )\n      a\n\n\nPython Callables\n----------------\n\nAs discussed earlier, Lupa allows Lua scripts to call Python functions\nand methods:\n\n.. code:: python\n\n      \u003e\u003e\u003e def add_one(num):\n      ...     return num + 1\n      \u003e\u003e\u003e lua_func = lua.eval('function(num, py_func) return py_func(num) end')\n      \u003e\u003e\u003e lua_func(48, add_one)\n      49\n\n      \u003e\u003e\u003e class MyClass():\n      ...     def my_method(self):\n      ...         return 345\n      \u003e\u003e\u003e obj = MyClass()\n      \u003e\u003e\u003e lua_func = lua.eval('function(py_obj) return py_obj:my_method() end')\n      \u003e\u003e\u003e lua_func(obj)\n      345\n\nLua doesn't have a dedicated syntax for named arguments, so by default\nPython callables can only be called using positional arguments.\n\nA common pattern for implementing named arguments in Lua is passing them\nin a table as the first and only function argument.  See\nhttp://lua-users.org/wiki/NamedParameters for more details.  Lupa supports\nthis pattern by providing two decorators: ``lupa.unpacks_lua_table``\nfor Python functions and ``lupa.unpacks_lua_table_method`` for methods\nof Python objects.\n\nPython functions/methods wrapped in these decorators can be called from\nLua code as ``func(foo, bar)``, ``func{foo=foo, bar=bar}``\nor ``func{foo, bar=bar}``.  Example:\n\n.. code:: python\n\n      \u003e\u003e\u003e @lupa.unpacks_lua_table\n      ... def add(a, b):\n      ...     return a + b\n      \u003e\u003e\u003e lua_func = lua.eval('function(a, b, py_func) return py_func{a=a, b=b} end')\n      \u003e\u003e\u003e lua_func(5, 6, add)\n      11\n      \u003e\u003e\u003e lua_func = lua.eval('function(a, b, py_func) return py_func{a, b=b} end')\n      \u003e\u003e\u003e lua_func(5, 6, add)\n      11\n\nIf you do not control the function implementation, you can also just\nmanually wrap a callable object when passing it into Lupa:\n\n.. code:: python\n\n      \u003e\u003e\u003e import operator\n      \u003e\u003e\u003e wrapped_py_add = lupa.unpacks_lua_table(operator.add)\n\n      \u003e\u003e\u003e lua_func = lua.eval('function(a, b, py_func) return py_func{a, b} end')\n      \u003e\u003e\u003e lua_func(5, 6, wrapped_py_add)\n      11\n\nThere are some limitations:\n\n1. Avoid using ``lupa.unpacks_lua_table`` and ``lupa.unpacks_lua_table_method``\n   for functions where the first argument can be a Lua table.  In this case\n   ``py_func{foo=bar}`` (which is the same as ``py_func({foo=bar})`` in Lua)\n   becomes ambiguous: it could mean either \"call ``py_func`` with a named\n   ``foo`` argument\" or \"call ``py_func`` with a positional ``{foo=bar}``\n   argument\".\n\n2. One should be careful with passing ``nil`` values to callables wrapped in\n   ``lupa.unpacks_lua_table`` or ``lupa.unpacks_lua_table_method`` decorators.\n   Depending on the context, passing ``nil`` as a parameter can mean either\n   \"omit a parameter\" or \"pass None\".  This even depends on the Lua version.\n\n   It is possible to use ``python.none`` instead of ``nil`` to pass None values\n   robustly.  Arguments with ``nil`` values are also fine when standard braces\n   ``func(a, b, c)`` syntax is used.\n\nBecause of these limitations lupa doesn't enable named arguments for all\nPython callables automatically.  Decorators allow to enable named arguments\non a per-callable basis.\n\n\nLua Coroutines\n--------------\n\nThe next is an example of Lua coroutines.  A wrapped Lua coroutine\nbehaves exactly like a Python coroutine.  It needs to get created at\nthe beginning, either by using the ``.coroutine()`` method of a\nfunction or by creating it in Lua code.  Then, values can be sent into\nit using the ``.send()`` method or it can be iterated over.  Note that\nthe ``.throw()`` method is not supported, though.\n\n.. code:: python\n\n      \u003e\u003e\u003e lua_code = '''\\\n      ...     function(N)\n      ...         for i=0,N do\n      ...             coroutine.yield( i%2 )\n      ...         end\n      ...     end\n      ... '''\n      \u003e\u003e\u003e lua = LuaRuntime()\n      \u003e\u003e\u003e f = lua.eval(lua_code)\n\n      \u003e\u003e\u003e gen = f.coroutine(4)\n      \u003e\u003e\u003e list(enumerate(gen))\n      [(0, 0), (1, 1), (2, 0), (3, 1), (4, 0)]\n\nAn example where values are passed into the coroutine using its\n``.send()`` method:\n\n.. code:: python\n\n      \u003e\u003e\u003e lua_code = '''\\\n      ...     function()\n      ...         local t,i = {},0\n      ...         local value = coroutine.yield()\n      ...         while value do\n      ...             t[i] = value\n      ...             i = i + 1\n      ...             value = coroutine.yield()\n      ...         end\n      ...         return t\n      ...     end\n      ... '''\n      \u003e\u003e\u003e f = lua.eval(lua_code)\n\n      \u003e\u003e\u003e co = f.coroutine()   # create coroutine\n      \u003e\u003e\u003e co.send(None)        # start coroutine (stops at first yield)\n\n      \u003e\u003e\u003e for i in range(3):\n      ...     co.send(i*2)\n\n      \u003e\u003e\u003e mapping = co.send(None)   # loop termination signal\n      \u003e\u003e\u003e sorted(mapping.items())\n      [(0, 0), (1, 2), (2, 4)]\n\nIt also works to create coroutines in Lua and to pass them back into\nPython space:\n\n.. code:: python\n\n      \u003e\u003e\u003e lua_code = '''\\\n      ...   function f(N)\n      ...         for i=0,N do\n      ...             coroutine.yield( i%2 )\n      ...         end\n      ...   end ;\n      ...   co1 = coroutine.create(f) ;\n      ...   co2 = coroutine.create(f) ;\n      ...\n      ...   status, first_result = coroutine.resume(co2, 2) ;   -- starting!\n      ...\n      ...   return f, co1, co2, status, first_result\n      ... '''\n\n      \u003e\u003e\u003e lua = LuaRuntime()\n      \u003e\u003e\u003e f, co, lua_gen, status, first_result = lua.execute(lua_code)\n\n      \u003e\u003e\u003e # a running coroutine:\n\n      \u003e\u003e\u003e status\n      True\n      \u003e\u003e\u003e first_result\n      0\n      \u003e\u003e\u003e list(lua_gen)\n      [1, 0]\n      \u003e\u003e\u003e list(lua_gen)\n      []\n\n      \u003e\u003e\u003e # an uninitialised coroutine:\n\n      \u003e\u003e\u003e gen = co(4)\n      \u003e\u003e\u003e list(enumerate(gen))\n      [(0, 0), (1, 1), (2, 0), (3, 1), (4, 0)]\n\n      \u003e\u003e\u003e gen = co(2)\n      \u003e\u003e\u003e list(enumerate(gen))\n      [(0, 0), (1, 1), (2, 0)]\n\n      \u003e\u003e\u003e # a plain function:\n\n      \u003e\u003e\u003e gen = f.coroutine(4)\n      \u003e\u003e\u003e list(enumerate(gen))\n      [(0, 0), (1, 1), (2, 0), (3, 1), (4, 0)]\n\n\nThreading\n---------\n\nThe following example calculates a mandelbrot image in parallel\nthreads and displays the result in PIL. It is based on a `benchmark\nimplementation`_ for the `Computer Language Benchmarks Game`_.\n\n.. _`Computer Language Benchmarks Game`: http://shootout.alioth.debian.org/u64/benchmark.php?test=all\u0026lang=luajit\u0026lang2=python3\n.. _`benchmark implementation`: http://shootout.alioth.debian.org/u64/program.php?test=mandelbrot\u0026lang=luajit\u0026id=1\n\n.. code:: python\n\n    lua_code = '''\\\n        function(N, i, total)\n            local char, unpack = string.char, table.unpack\n            local result = \"\"\n            local M, ba, bb, buf = 2/N, 2^(N%8+1)-1, 2^(8-N%8), {}\n            local start_line, end_line = N/total * (i-1), N/total * i - 1\n            for y=start_line,end_line do\n                local Ci, b, p = y*M-1, 1, 0\n                for x=0,N-1 do\n                    local Cr = x*M-1.5\n                    local Zr, Zi, Zrq, Ziq = Cr, Ci, Cr*Cr, Ci*Ci\n                    b = b + b\n                    for i=1,49 do\n                        Zi = Zr*Zi*2 + Ci\n                        Zr = Zrq-Ziq + Cr\n                        Ziq = Zi*Zi\n                        Zrq = Zr*Zr\n                        if Zrq+Ziq \u003e 4.0 then b = b + 1; break; end\n                    end\n                    if b \u003e= 256 then p = p + 1; buf[p] = 511 - b; b = 1; end\n                end\n                if b ~= 1 then p = p + 1; buf[p] = (ba-b)*bb; end\n                result = result .. char(unpack(buf, 1, p))\n            end\n            return result\n        end\n    '''\n\n    image_size = 1280   # == 1280 x 1280\n    thread_count = 8\n\n    from lupa import LuaRuntime\n    lua_funcs = [ LuaRuntime(encoding=None).eval(lua_code)\n                  for _ in range(thread_count) ]\n\n    results = [None] * thread_count\n    def mandelbrot(i, lua_func):\n        results[i] = lua_func(image_size, i+1, thread_count)\n\n    import threading\n    threads = [ threading.Thread(target=mandelbrot, args=(i,lua_func))\n                for i, lua_func in enumerate(lua_funcs) ]\n    for thread in threads:\n        thread.start()\n    for thread in threads:\n        thread.join()\n\n    result_buffer = b''.join(results)\n\n    # use Pillow to display the image\n    from PIL import Image\n    image = Image.frombytes('1', (image_size, image_size), result_buffer)\n    image.show()\n\nNote how the example creates a separate ``LuaRuntime`` for each thread\nto enable parallel execution.  Each ``LuaRuntime`` is protected by a\nglobal lock that prevents concurrent access to it.  The low memory\nfootprint of Lua makes it reasonable to use multiple runtimes, but\nthis setup also means that values cannot easily be exchanged between\nthreads inside of Lua.  They must either get copied through Python\nspace (passing table references will not work, either) or use some Lua\nmechanism for explicit communication, such as a pipe or some kind of\nshared memory setup.\n\n\nRestricting Lua access to Python objects\n----------------------------------------\n\n..\n        \u003e\u003e\u003e try: unicode = unicode\n        ... except NameError: unicode = str\n\nLupa provides a simple mechanism to control access to Python objects.\nEach attribute access can be passed through a filter function as\nfollows:\n\n.. code:: python\n\n        \u003e\u003e\u003e def filter_attribute_access(obj, attr_name, is_setting):\n        ...     if isinstance(attr_name, unicode):\n        ...         if not attr_name.startswith('_'):\n        ...             return attr_name\n        ...     raise AttributeError('access denied')\n\n        \u003e\u003e\u003e lua = lupa.LuaRuntime(\n        ...           register_eval=False,\n        ...           attribute_filter=filter_attribute_access)\n        \u003e\u003e\u003e func = lua.eval('function(x) return x.__class__ end')\n        \u003e\u003e\u003e func(lua)\n        Traceback (most recent call last):\n         ...\n        AttributeError: access denied\n\nThe ``is_setting`` flag indicates whether the attribute is being read\nor set.\n\nNote that the attributes of Python functions provide access to the\ncurrent ``globals()`` and therefore to the builtins etc.  If you want\nto safely restrict access to a known set of Python objects, it is best\nto work with a whitelist of safe attribute names.  One way to do that\ncould be to use a well selected list of dedicated API objects that you\nprovide to Lua code, and to only allow Python attribute access to the\nset of public attribute/method names of these objects.\n\nSince Lupa 1.0, you can alternatively provide dedicated getter and\nsetter function implementations for a ``LuaRuntime``:\n\n.. code:: python\n\n        \u003e\u003e\u003e def getter(obj, attr_name):\n        ...     if attr_name == 'yes':\n        ...         return getattr(obj, attr_name)\n        ...     raise AttributeError(\n        ...         'not allowed to read attribute \"%s\"' % attr_name)\n\n        \u003e\u003e\u003e def setter(obj, attr_name, value):\n        ...     if attr_name == 'put':\n        ...         setattr(obj, attr_name, value)\n        ...         return\n        ...     raise AttributeError(\n        ...         'not allowed to write attribute \"%s\"' % attr_name)\n\n        \u003e\u003e\u003e class X:\n        ...     yes = 123\n        ...     put = 'abc'\n        ...     noway = 2.1\n\n        \u003e\u003e\u003e x = X()\n\n        \u003e\u003e\u003e lua = lupa.LuaRuntime(attribute_handlers=(getter, setter))\n        \u003e\u003e\u003e func = lua.eval('function(x) return x.yes end')\n        \u003e\u003e\u003e func(x)  # getting 'yes'\n        123\n        \u003e\u003e\u003e func = lua.eval('function(x) x.put = \"ABC\"; end')\n        \u003e\u003e\u003e func(x)  # setting 'put'\n        \u003e\u003e\u003e print(x.put)\n        ABC\n        \u003e\u003e\u003e func = lua.eval('function(x) x.noway = 42; end')\n        \u003e\u003e\u003e func(x)  # setting 'noway'\n        Traceback (most recent call last):\n         ...\n        AttributeError: not allowed to write attribute \"noway\"\n\n\nRestricting Lua Memory Usage\n----------------------------\n\nLupa provides a simple mechanism to control the maximum memory\nusage of the Lua Runtime since version 2.0.\nBy default Lupa does not interfere with Lua's memory allocation, to opt-in\nyou must set the ``max_memory`` when creating the LuaRuntime.\n\nThe ``LuaRuntime`` provides three methods for controlling and reading the\nmemory usage:\n\n1. ``get_memory_used(total=False)`` to get the current memory\n   usage of the LuaRuntime.\n\n2. ``get_max_memory(total=False)`` to get the current memory limit.\n   ``0`` means there is no memory limitation.\n\n3. ``set_max_memory(max_memory, total=False)`` to change the memory limit.\n   Values below or equal to 0 mean no limit.\n\nThere is always some memory used by the LuaRuntime itself (around ~20KiB,\ndepending on your lua version and other factors) which is excluded from all\ncalculations unless you specify ``total=True``.\n\n.. code:: python\n\n        \u003e\u003e\u003e from lupa import lua52\n        \u003e\u003e\u003e lua = lua52.LuaRuntime(max_memory=0)  # 0 for unlimited, default is None\n        \u003e\u003e\u003e lua.get_memory_used()  # memory used by your code\n        0\n        \u003e\u003e\u003e total_lua_memory = lua.get_memory_used(total=True)  # includes memory used by the runtime itself\n        \u003e\u003e\u003e assert total_lua_memory \u003e 0  # exact amount depends on your lua version and other factors\n\n\nLua code hitting the memory limit will receive memory errors:\n\n.. code:: python\n      \n        \u003e\u003e\u003e lua.set_max_memory(100)\n        \u003e\u003e\u003e lua.eval(\"string.rep('a', 1000)\")   # doctest: +IGNORE_EXCEPTION_DETAIL\n        Traceback (most recent call last):\n         ...\n        lupa.LuaMemoryError: not enough memory\n\n``LuaMemoryError`` inherits from ``LuaError`` and ``MemoryError``.\n\n\nImporting Lua binary modules\n----------------------------\n\n**This will usually work as is**, but here are the details, in case\nanything goes wrong for you.\n\nTo use binary modules in Lua, you need to compile them against the\nheader files of the LuaJIT sources that you used to build Lupa, but do\nnot link them against the LuaJIT library.\n\nFurthermore, CPython needs to enable global symbol visibility for\nshared libraries before loading the Lupa module.  This can be done by\ncalling ``sys.setdlopenflags(flag_values)``.  Importing the ``lupa``\nmodule will automatically try to set up the correct ``dlopen`` flags\nif it can find the platform specific ``DLFCN`` Python module that\ndefines the necessary flag constants.  In that case, using binary\nmodules in Lua should work out of the box.\n\nIf this setup fails, however, you have to set the flags manually.\nWhen using the above configuration call, the argument ``flag_values``\nmust represent the sum of your system's values for ``RTLD_NEW`` and\n``RTLD_GLOBAL``.  If ``RTLD_NEW`` is 2 and ``RTLD_GLOBAL`` is 256, you\nneed to call ``sys.setdlopenflags(258)``.\n\nAssuming that the Lua luaposix_ (``posix``) module is available, the\nfollowing should work on a Linux system:\n\n.. code:: python\n\n      \u003e\u003e\u003e import sys\n      \u003e\u003e\u003e orig_dlflags = sys.getdlopenflags()\n      \u003e\u003e\u003e sys.setdlopenflags(258)\n      \u003e\u003e\u003e import lupa\n      \u003e\u003e\u003e sys.setdlopenflags(orig_dlflags)\n\n      \u003e\u003e\u003e lua = lupa.LuaRuntime()\n      \u003e\u003e\u003e posix_module = lua.require('posix')     # doctest: +SKIP\n\n.. _luaposix: http://git.alpinelinux.org/cgit/luaposix\n\n\nBuilding with different Lua versions\n------------------------------------\n\nThe build is configured to automatically search for an installed version\nof first LuaJIT and then Lua, and failing to find either, to use the bundled\nLuaJIT or Lua version.\n\nIf you wish to build Lupa with a specific version of Lua, you can\nconfigure the following options on setup:\n\n.. list-table::\n   :widths: 20 35\n   :header-rows: 1\n\n   * - Option\n     - Description\n   * - ``--lua-lib \u003clibfile\u003e``\n     - Lua library file path, e.g. ``--lua-lib /usr/local/lib/lualib.a``\n   * - ``--lua-includes \u003cincdir\u003e``\n     - Lua include directory, e.g. ``--lua-includes /usr/local/include``\n   * - ``--use-bundle``\n     - Use bundled LuaJIT or Lua instead of searching for an installed version.\n   * - ``--no-bundle``\n     - Don't use the bundled LuaJIT/Lua, search for an installed version of LuaJIT or Lua,\n       e.g. using ``pkg-config``.\n   * - ``--no-lua-jit``\n     - Don't use or search for LuaJIT, only use or search Lua instead.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscoder%2Flupa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscoder%2Flupa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscoder%2Flupa/lists"}