{"id":15009007,"url":"https://github.com/dyalog/pynapl","last_synced_at":"2025-04-06T20:10:47.166Z","repository":{"id":41536538,"uuid":"102481506","full_name":"Dyalog/pynapl","owner":"Dyalog","description":"Dyalog APL ←→ Python interface","archived":false,"fork":false,"pushed_at":"2025-01-20T14:58:08.000Z","size":960,"stargazers_count":78,"open_issues_count":12,"forks_count":13,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-03-30T18:10:02.092Z","etag":null,"topics":["apl","apl-python-interface","bridge","dyalog","dyalog-apl","interface","python","python-2","python-3","python2","python3"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Dyalog.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2017-09-05T12:59:25.000Z","updated_at":"2025-03-27T16:32:44.000Z","dependencies_parsed_at":"2025-03-15T18:31:08.701Z","dependency_job_id":null,"html_url":"https://github.com/Dyalog/pynapl","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dyalog%2Fpynapl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dyalog%2Fpynapl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dyalog%2Fpynapl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Dyalog%2Fpynapl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Dyalog","download_url":"https://codeload.github.com/Dyalog/pynapl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247543591,"owners_count":20955865,"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":["apl","apl-python-interface","bridge","dyalog","dyalog-apl","interface","python","python-2","python-3","python2","python3"],"created_at":"2024-09-24T19:22:22.063Z","updated_at":"2025-04-06T20:10:47.143Z","avatar_url":"https://github.com/Dyalog.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Py'n'APL: APL-Python interface\n\nThis is an interface between Dyalog APL and Python. It allows Python\ncode to be accessed from APL, and vice versa.\n\n#### Requirements:\n\n - Dyalog APL version 16.0 Unicode\n - Python 2.7.9 or higher, or Python 3.4 or higher.\n\n## User manual\n\n### Accessing Python from APL\n\nThe APL side of the interface is located in `Py.dyalog`. \nIt can be loaded into the workspace using:\n\n```apl\n]load Py\n```\n\nNote that it expects the included Python scripts to be\nin the same directory as the `Py.dyalog` file.\n\n#### Starting a Python interpreter\n\nTo start a Python interpreter, make a new instance of the\n`Py.Py` class. This will start a Python instance in the background,\nand connect to it. On Unix, this is done using two pipes; on Windows\nthis is done using a TCP connection.\n\n```apl\npy ← ⎕NEW Py.Py\n```\n\nThe resulting object can be used to interact with the Python\ninterpreter. Once the object is destroyed, the Python interpreter\nassociated with it will also be shut down.\n\nThere are several different options that can be given to the Py\nclass, namely:\n\n| Option | Argument | Purpose |\n| --- | --- | --- |\n| `Attach` | ignored | Do not start up a Python instance, but allow attachment to one that is already running. An input and output pipe will be given (or a port number in TCP mode), and it will wait for a connection from the Python side. The Python side can be told to connect using `APL.client(in,out)` (or `APL.tcpclient(port)`). |\n| `ForceTCP` | boolean | Use TCP mode even on Unix. This may be necessary for non-standard interpreters. |\n| `PyPath` | path to an interpreter | Start the Python interpreter given in the argument, instead of the system one. |\n| `ArgFmt` | string, where `⍎` will be replaced by the path to the slave script, `→` by the input pipe file (or `TCP` if in TCP mode), and `←` by the output pipe file (or port number if in TCP mode) | When used in combination with `PyPath`, use a custom argument format rather than the standard one. |\n| `Version` | major Python version (2 or 3) | Start either a Python 2 or 3 interpreter, depending on which is given. The default is currently 3. |\n| `Debug` | boolean | If the boolean is 1, turns on debug messages and also does not start up a Python instance. |\n| `NoInterrupts` | boolean | Turns off interrupts in the interface code. This disables the ability to interrupt running Python code, but makes sure that any interrupts are caught by your own code and not by the interface. |\n| `NoDF` | boolean | Turns off automatically setting ⎕DF when importing Python objects. This saves a repr() call (from APL) per object. |\n\nIn particular, the following might be of interest:\n\n```apl\npy ← ⎕NEW Py.Py('Version' 2) ⍝ use Python 2 instead of 3\n```\n\n```apl\n⍝ start a Blender instance and control that instead of a normal Python\n⍝ (if on Windows, you have to pass in the absolute path to blender.exe instead)\npy ← ⎕NEW Py.Py (('PyPath' 'blender') ('ArgFmt' '-P \"⍎\" -- → ← thread') ('ForceTCP' 1))\n```\n\n\n\n#### Running Python statements\n\nThe `Exec` function can be used to run one or more Python\nstatements. It takes one string, which may have newlines in it.\nThe `Py.ScriptFollows` function can be used to help load scripts.\n\n```apl\n⍝ run one statement\npy.Exec 'import antigravity'\n\n⍝ run a script\npy.Exec 'def foobar():',(⎕UCS 10),'  return \"abc\"'\n\n⍝ or (in a tradfn):\npy.Exec #.Py.ScriptFollows\n⍝ def foobar():\n⍝    return \"abc\"\n```\n\nAn `APL` object will be available to the Python code, in order\nfor it to call back into the APL code. (See [Accessing APL from Python](#accessing-apl-from-python) for more information.)\n\n#### Evaluating Python expressions\n\nThe `Eval` function can be used to evaluate a Python expression.\nIt takes as its left argument the Python expression to be evaluated,\nand as its right argument a vector of APL arguments to be substituted\ninto it. Inside the Python expression, the quad (`⎕`) or the quote-quad\n(`⍞`) can be used to refer to these arguments. If the quad is used,\nthe argument will be converted to a (hopefully) suitable Python\nrepresentation first; if the quote-quad is used, the argument will be\nexposed on the Python side as an `APLArray` object. (See [Data\nconversion](#data-conversion) for more information.)\n\nIf the Python expression returns something other than an `APLArray`,\nit will be converted back into a suitable APL form before being sent back\nto APL.\n\n```apl\n     ⍝ access a variable\n     py.Eval '__name__'\npynapl.PyEvaluator\n\n     ⍝ add two numbers\n     '⎕+⎕' py.Eval 2 2\n4\n\n     ⍝ this is equivalent to ⍴X\n     '⍞.rho' py.Eval ⊂5 5⍴⎕A\n5 5\n\n     ⍝ round trip\n     'APL.eval(\"2+2\")' py.Eval ⍬\n4\n\n     ⍝ set a variable on the Python side\n     'x' py.Set 42\n     py.Eval 'x'\n42\n\n     ⍝ alternate syntax when there are no arguments\n     py.Eval 'APL.eval(\"2+2\")' \n4\n```\n\nJust as with `Exec`, an `APL` object will be made available to the\nPython expression.\n\n#### Making Python functions available to APL\n\nIt is also possible to 'import' a Python function to the APL workspace.\nThe `PyFn` function can be used to create APL functions that call\nPython functions automatically.\n\nThe `PyFn` function returns a namespace containing two functions,\n`Call` and `CallVec`. \n\n * `CallVec` takes a vector of arguments as its\nright argument, and passes those into the Python function. It takes an\noptional boolean vector as its left argument, which describes\nwhether or not to convert the arguments.\n\n * `Call` is a \"normal\" APL function. If used monadically, it calls\nthe Python function with one argument (`f(⍵)`); if used dyadically\nit calls the Python function with two arguments (`f(⍺,⍵)`). The\narguments are always converted.\n\nThe namespace also includes a reference to the Py object that created\nit, so it will not be destroyed until such functions themselves are.\n\nExample:\n\n```apl\n\n⍝ import a Python module\npy.Exec 'import webbrowser'\n\n⍝ define a function from it in APL\n⍝ this one handily takes only one argument so can be used monadically\nshowPage←(py.PyFn 'webbrowser.open').Call\n\n⍝ this will now show a web page\nshowPage 'http://www.dyalog.com'\n```\n\n#### Making Python modules and objects available to APL\n\nBy default, Python objects that have APL equivalents are automatically \nconverted. E.g., a Python list becomes an APL vector. (See the\n\"Data Conversion\" section.) \n\nPython objects that do not have such equivalents are sent as references\ninstead, which can be used on the APL side to access their attributes.\nOn the APL side, a stub class will be instantiated which will have\nattributes corresponding to the Python ones.\n\n```apl\n      py.Exec'import sys'\n      sys←py.Eval'sys'\n      sys.version_info\n3 5 3  final  0\n\n      5↑sys.⎕NL¯2\n __doc__  __name__  __package__  __stderr__  __stdin__ \n```\n\nFields are exposed on the APL side by means of properties, which can be\nused to set and retrieve the values, and methods are exposed as functions\nwhich can be called:\n\n```apl\n      os←py.Import'os' ⍝ convenience functions\n      +os.getpid ⍬\n17906\n```\n\nSuch functions return a shy result, and take a right argument consisting\nof a vector of positional arguments, and an optional left argument representing \nthe keyword arguments. This left argument may either be a namespace or a\nlist of key-value pairs.\n\n```apl\n      json←py.Import'json'\n      +(⊂'separators' '--')json.dumps ⊂1 2 3 4\n[1-2-3-4]\n```\n\nIt is also possible to send these references back to Python and interact\nwith them there:\n\n```apl\n      '⎕.getpid()' py.Eval os\n17906\n```\n\nThe resulting classes cannot be instantiated from APL using `⎕NEW`, they\ncan only be instantiated by calling the Python constructors. The objects\nkeep a reference to the `Py` instance that created them, which means\nthe Python interpreter will stay alive as long as any of its objects\nare still around. On the Python side, the objects are stored by the\ninterface, and released when all APL references to them have been removed.\n\nIn some instances, some name mangling is required. Variables are exposed as\nproperties on the APL side, which means that a variable `foo` will conflict\nwith functions named `get_foo` and `set_foo`. In this case, those functions\nwill be renamed `⍙get_foo` and `⍙set_foo`. \n\nIn APL, a Python object reference will have the following members:\n\n - Properties:\n    - `foo`: for each non-callable attribute `foo` in the object, gets or sets that attribute\n    - `∆foo`: for each non-callable attribute `foo` in the object, gets that attribute\n               without object translation (i.e., will _always_ return a Python\n               object, even for lists and numbers).\n - Functions:\n    - `bar`: for each callable attribute `bar` in the object, calls it and returns the result. \n    - `∆bar`: for each callable attribute `bar` in the object, calls it and returns the result\n               without object translation.\n\n    - `⍙DF`: returns the string representation of the object (using `repr`), and also sets\n              the display form to it.\n    - `⍙Get`: retrieves an attribute, given as a character vector as the right argument\n    - `⍙Get∆`: retrieves an attribute, given as a character vector as the right argument,\n                without object translation on the result.\n    - `⍙Set`: sets the attribute (left argument) to the value given as the right argument\n    - `⍙SetRaw`: sets the attribute (left argument) to the value given as the right argument,\n                  using `⍞` instead of `⎕`.\n    - `⍙Call`: calls the function (left argument) with the given positional and keyword arguments\n                 (right argument, both must be present).\n    - `⍙Call∆`: like `⍙Call`, but without object translation on the result.\n\n\n#### Error handling\n\nIf the Python code raises an exception, the interface will signal a\nDOMAIN ERROR. `⎕DMX.Message` will contain the string representation\nof the Python exception. \n\n### Accessing APL from Python\n\nThe `APL.py` module contains a function that will start an APL\ninterpreter. Just like the APL side, it expects the `Py.dyalog`\nscript to be in the same directory. \n\n#### Starting an APL interpreter\n\nAn APL object can be obtained using the `APL.APL` function. This\nwill start a Dyalog instance in the background and connect to it.\n\n```python\nfrom pynapl import APL\napl = APL.APL()\n```\n\nAn optional `dyalog` argument may be given to the `APL` function,\nto specify the path to the `dyalog` interpreter. If it is not given,\non Unix the `dyalog` interpreter on the path will be used,\non Windows the registry will be consulted. \nThe Dyalog instance will be shut down once the `apl` object is\ndestroyed.\n\n#### Fixing an APL script\n\nThe `fix` function takes a string, which will be 2⎕FIX'ed on the\nAPL side. This can be used to load large amounts of APL code into\nthe interpreter. \n\n```python\napl.fix(\"\"\"\n:Namespace Test\nfoo←42\n:EndNamespace\n\"\"\")\n```\n\n#### Evaluating APL code\n\nThe `eval` function takes a string, which will be evaluated\nusing `⍎` on the APL side. Any extra arguments passed into\n`eval` will be put into a vector and exposed as `∆` on the\nAPL side, and a `py` object will be available for the APL code\nto communicate back to the Python interpreter. (See [Accessing\nPython from APL](#accessing-python-from-apl).)\n\nThis is a relatively low-level function, and it is probably better\nto use `fn` and `op`. \n\nConversion of data *to* APL types is done automatically. (Anything\nthat's not an `APLArray` is converted.) The result of the evaluation\nis converted back to the Python format unless `raw` is set.\n\n```\n\u003e\u003e\u003e apl.eval(\"2+2\")\n4\n\u003e\u003e\u003e apl.eval(\"⎕A\")\nu'ABCDEFGHIJKLMNOPQRSTUVWXYZ'\n\u003e\u003e\u003e apl.eval(\"⎕A\", raw=True)\n\u003cArray.APLArray object at 0x7fb704e36310\u003e\n\u003e\u003e\u003e apl.eval(\"'2+2' py.Eval ⍬\") # round trip\n4\n```\n\n#### Making APL functions available to Python\n\nThe `fn` function can be used to import an APL function to Python.\nThe function may be niladic (if called with no arguments),\nmonadic (if called with one argument), or dyadic (if called with\ntwo).\n\nAs with `eval`, a named argument `raw` can be set to prevent\nautomatic data conversion.\n\n```\n\u003e\u003e\u003e aplsum = apl.fn(\"+/\")\n\u003e\u003e\u003e aplsum([1,2,3,4,5])\n15\n\u003e\u003e\u003e aplsum(3, [1,2,3,4,5])\n[6, 9, 12]\n```\n\nThe function may be an anonymous dfn and may contain newlines.\nIt may *not* be a definition of a tradfn (those can be defined\nusing `fix` or `tradfn`, then referred to by name using `fn`).\n\n```python\n\u003e\u003e\u003e factorial = apl.fn(\"\"\"\n{ ⍵≤0:1\n  ⍵×∇⍵-1\n}\n\"\"\")\n\u003e\u003e\u003e factorial(5)\n120\n```\n\n##### Defining a tradfn using Python\n\nApart from using `fix`, a tradfn can also be defined using the\n`tradfn` function:\n\n```python\n\u003e\u003e\u003e foo = apl.tradfn(\"\"\"\nr←foo x\nr←x+x\n\"\"\")\n\u003e\u003e\u003e foo(5)\n10\n\u003e\u003e\u003e apl.fn(\"foo\")(5)\n10\n```\n\n#### Making APL operators available to Python\n\nPython does not make the difference between functions and\noperators that APL makes. Therefore, APL operators can be\nexposed as Python functions using the `op` function.\n\n```\n\u003e\u003e\u003e scan = apl.op(\"\\\\\") # note the extra backslash for escaping\n```\n\nThe operator is then exposed as a Python function, which takes \none or two arguments, depending on whether the operator is \nmonadic or dyadic.\n\nThe arguments may be either values or Python functions.\nIf you want to pass an APL function to an APL operator via Python,\nyou must first import the APL function using `fn`. \n\n```\n\u003e\u003e\u003e apl_add = apl.fn(\"+\")\n\u003e\u003e\u003e py_add = lambda x, y: x+y\n\u003e\u003e\u003e apl_sumscan = scan(apl_add) # equivalent to \"+\\\"\n\u003e\u003e\u003e py_sumscan = scan(py_add)   # uses the Python \"+\"\n\u003e\u003e\u003e apl_sumscan([1,2,3])\n[1, 3, 6]\n\u003e\u003e\u003e py_sumscan([1,2,3])\n[1, 3, 6]\n```\n\nIf an APL operator is applied to an APL function via Python,\nas in the `apl_sumscan` example, this is detected, and the application\nis done in APL without calling back into Python. \n\n#### Using APL objects from Python\n\nJust as APL may make use of Python objects, Python may make use of\nAPL objects.\n\nIf a call to `eval` or to an APL function returns an instance of an\nAPL object, it is represented on the Python side by an instance of the\n`APLObject` class, in which public functions and variables will appear\nas attributes.\n\n```\n\u003e\u003e\u003e apl.fix(\"\"\"\n... :Class foo\n...    :Field Public n←0\n...    ∇x←getN\n...       :Access Public\n...       x←n\n...    ∇\n...    ∇setN x\n...       :Access Public\n...       n←x\n...    ∇\n... :EndClass\n... \"\"\")\n['foo']\n\u003e\u003e\u003e a = apl.eval(\"+a ← ⎕NEW foo\")\n\u003e\u003e\u003e a\n\u003cpynapl.Array.APLObject object at 0x7f1d3a9e0ac8\u003e\n\u003e\u003e\u003e a.n\n0\n\u003e\u003e\u003e a.getN()\n0\n\u003e\u003e\u003e a.setN(20)\n[]\n\u003e\u003e\u003e a.n\n20\n\u003e\u003e\u003e a.n = 30\n\u003e\u003e\u003e a.getN()\n30\n```\n\nThe `APLObject` class contains a reference to the object on the APL side,\nso changes to its state are reflected on the APL side, and vice versa:\n\n```\n\u003e\u003e\u003e apl.eval(\"a.n\")\n30\n\u003e\u003e\u003e apl.eval(\"a.n←40\")\n40\n\u003e\u003e\u003e a.n\n40\n```\n\nMembers whose names are not valid names in Python can be accessed via `getattr`\nand `setattr`. Python 2 requires attribute names to be ASCII only, so members whose\nnames contain non-ASCII characters cannot be accessed. Python 3 does not have\nthis limitation. \n\n#### Error handling\n\nIf a signal is raised by the APL code, an APLError will be raised\non the Python side. The exception object will contain a `dmx` field,\nwhich is a dictionary that contains the fields from `⎕DMX`. \n\nWhen an interrupt is raised, the message will be `\"Interrupt\"` and\n`dmx` will be `None`. \n\n### Data conversion\n\n#### From Python to APL\n\n * Numbers (any kind) or boolean: number\n * One-character strings: characters\n * Other string: character vector\n * List or tuple: vector\n * NoneType: empty numeric vector\n * Dictionary: namespace\n\nIn addition, any kind of iterable object (objects that are instances\nof `collections.Iterable` or that implement `__iter__`, and objects\nthat implement both `__len__` and `__getitem__`) will be iterated over,\nand the results sent as a vector to APL. This allows for most kinds of\ncustom container objects to be used. \n\n_NOTE_: if the object is an infinite generator, it will cause a hang.\n\nIf the numpy library is available, numpy matrices will be automatically\nconverted to APL matrices. \n\nIf the object is none of these, an object reference will be sent to APL,\nwhere it can be used to access its attributes. Python code can also send an\nobject reference explicitly by using the `apl.obj` function.\n\n```apl\n     py.Eval 'sys' ⍝ ask for module object\n#.Py.⍙PythonObject.[module]\n     py.Eval '[1,2,3,4]' ⍝ send a list\n1 2 3 4\n     py.Eval 'apl.obj([1,2,3,4])' ⍝ send a list as an object\n#.Py.⍙PythonObject.[list]\n```\n\n\n#### From APL to Python\n\n * Numbers: int, long, or float, depending on which fits best\n * Simple (non-nested) character vector: Unicode string\n * Numeric vector / nested vector: List\n * Higher-rank array: nested list (the equivalent of\n   `{↓⍣((⊃⍴⍴⍵)-1)⊢⍵}` is done).\n * Namespace containing values: dictionary\n\n#### The `APLArray` class\n\nThis is a class on the Python side that can be used to communicate\nwith APL without going through the conversion. It is a multidimensional\narray, which may contain nested APLArray objects.\n\nAn APLArray object can be indexed using a list or a tuple.\nThe index should be given as if `⎕IO=0`, e.g.:\n\n```\n\u003e\u003e\u003e foo = apl.eval(\"5 5⍴⎕A\", raw=True)\n\u003e\u003e\u003e foo.rho\n[5, 5]\n\u003e\u003e\u003e foo[2,3]  # ⎕IO←0 ⋄ (5 5⍴⎕A)[2;3]\nu'N'\n```\n\nAssignment to individual items is possible in the same manner.\nConversion will be done automatically if it is necessary.\nAn `IndexError` will be raised if the coordinates are out of\nrange.\n\n## Implementation details\n\nOn Unix, there are two ways in which the connection between APL and\nPython can be made. \n\n 1. The default way is by using two named pipes, which\nthe initiating side will create (using `mkfifo`) and pass to the\nclient program. \n\n 2. It can also be set to use a TCP connection. The initiating\n side will start up a TCP server (using Conga on the APL side)\n on an unused port, and listen for a connection from the client side.\n\nOn Windows, only TCP mode is supported. On Unix, TCP mode may be\nnecessary to use non-standard interpreters (Blender in particular\ndoes not like pipes much). TCP mode has about twice the latency as\npipe mode. \n\n### Communication\n\nThe both programs communicate by sending each other messages,\nas described below.\n\n#### Message Format\n\nThe underlying format used for messages consists of a 5-byte header\nand then a body.\n\nThe first byte of the header denotes the message type, the next four\ngive the length of the body in bytes (high-endian). \n\nThe contents of the body are UTF-8 encoded text, usually JSON.\n\n#### Message Types\n\n| Message Type | Contents | Purpose |\n| --- | --- | --- |\n| `0` (`OK`) | ignored | returned to signal nothing has gone wrong |\n| `1` (`PID`) | the PID of the process, as UTF-8 text | sent by the client on startup |\n| `2` (`STOP`) | ignored | tells the client to shut down |\n| `3` (`REPR`) | an UTF8-encoded string of code | runs the code on the other side and sends `REPRRET` back with the string representation of the result (for debugging) |\n| `4` (`EXEC`) | an UTF8-encoded string of code, which does not return a value | runs the code on the other side, and sends back `ERR` or `OK`. For APL this `⎕FIX`es the code |\n| `5` (`REPRRET`) | an UTF8-encoded string | sends back the result of an earlier `REPR` |\n| `10` (`EVAL`) | a JSON array of two elements, the first being a string of code and the second being an array of serialized objects | evaluates the expression given the arguments, and sends back the result using `EVALRET` |\n| `11` (`EVALRET`) | a serialized object | the result of an earlier `EVAL` |\n| `253` (`DBGSerializationRoundTrip`) | a serialized object | deserializes and reserializes the object on the other side, then sends the result back using the same message code (for debugging) |\n| `255` (`ERR`) | an UTF-8 string containing the description of the error | signal an error |\n\n#### JSON messages\n\n##### `EVAL`: \n\nThe `EVAL` message is a JSON list containing two elements. The first element\nshould be a string containing the expression to evaluate, the second element\nshould be a (possibly empty) list of arguments. \n\n###### `ERR`:\n\nThe `ERR` message is a JSON dictionary containing at least a `Message` field,\nwhich contains the error message. Errors coming from APL may also contain a\n`DMX` field, which contains the JSON representation of Dyalog APL's `⎕DMX`\nobject.\n\n#### Reentrancy\n\nThe message handling code on both sides supports handling messages while\nwaiting for the result of another. E.g., if an `EVAL` is sent, it may\ncause another `EVAL` to be sent back, which will then be handled before\nthe corresponding `EVALRET` is received. This way, the evaluation of an\nexpression may switch back and forth between the two sides as needed.\n\nExample:\n\n```\nPython side:\n\u003e\u003e\u003e x = apl.eval(\"2+'2+2' py.Eval ⍬\")\n # Python sends to APL: EVAL '2+2' py.Eval ⍬\n \nAPL side:\n     2+'2+2' py.Eval ⍬\n ⍝ APL sends back to Python: EVAL 2+2\n\nPython side:\n\u003e\u003e\u003e 2+2\n # this evaluates to 4\n # Python sends to APL: EVALRET 4\n\nAPL side:\n ⍝ receives EVALRET 4\n     2 + 4\n ⍝ this evaluates to 6\n ⍝ APL sends to Python: EVALRET 6\n\nPython side:\n # receives EVALRET 6\n # the final answer is 6 \n\u003e\u003e\u003e x\n6\n```\n\n## Testing\n\nTo run unit tests from Python:\n```sh\n$ python2 -m unittest pynapl.test.test_APL pynapl.test.test_Array\n$ python3 -m unittest pynapl.test.test_APL pynapl.test.test_Array\n```\n\nTo run unit tests from APL:\n```apl\n      ]load pynapl/Py       \n      ]load pynapl/test/Test\n      Test.RunTests 2 ⍝ test with Python 2\n      Test.RunTests 3 ⍝ test with Python 3\n```\n\nThere is also an interactive GUI test that uses [TkInter](https://wiki.python.org/moin/TkInter):\n```apl\n      ]load pynapl/Py\n      ]load pynapl/PyTest\n      PyTest.PyTest\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdyalog%2Fpynapl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdyalog%2Fpynapl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdyalog%2Fpynapl/lists"}