{"id":13803977,"url":"https://github.com/bendudson/py4cl","last_synced_at":"2025-04-08T03:15:48.765Z","repository":{"id":34386168,"uuid":"164507515","full_name":"bendudson/py4cl","owner":"bendudson","description":"Call python from Common Lisp","archived":false,"fork":false,"pushed_at":"2023-10-28T16:05:45.000Z","size":789,"stargazers_count":241,"open_issues_count":14,"forks_count":33,"subscribers_count":13,"default_branch":"master","last_synced_at":"2025-03-31T18:21:56.390Z","etag":null,"topics":["common-lisp","python"],"latest_commit_sha":null,"homepage":null,"language":"Common Lisp","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/bendudson.png","metadata":{"files":{"readme":"README.org","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":"2019-01-07T22:32:53.000Z","updated_at":"2025-03-14T13:58:40.000Z","dependencies_parsed_at":"2024-05-02T10:06:15.318Z","dependency_job_id":"d2f9b342-977f-4855-8849-9522db8b9e34","html_url":"https://github.com/bendudson/py4cl","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/bendudson%2Fpy4cl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bendudson%2Fpy4cl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bendudson%2Fpy4cl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bendudson%2Fpy4cl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bendudson","download_url":"https://codeload.github.com/bendudson/py4cl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247767236,"owners_count":20992548,"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":["common-lisp","python"],"created_at":"2024-08-04T01:00:39.794Z","updated_at":"2025-04-08T03:15:48.724Z","avatar_url":"https://github.com/bendudson.png","language":"Common Lisp","readme":"[[https://travis-ci.org/bendudson/py4cl][https://travis-ci.org/bendudson/py4cl.svg?branch=master]]\n\n* Introduction\n\nPy4CL is a bridge between Common Lisp and Python, which enables Common\nLisp to interact with Python code. It uses streams to communicate with\na separate python process, the approach taken by [[https://github.com/marcoheisig/cl4py][cl4py]]. This is\ndifferent to the CFFI approach used by [[https://github.com/pinterface/burgled-batteries][burgled-batteries]], but has the\nsame goal. \n\n** Installing\n\nDepends on:\n\n - Currently tested with SBCL, CCL and ECL (after 2016-09-06). CLISP\n   doesn't (yet) have =uiop:launch-program=.\n - ASDF3 version 3.2.0 (Jan 2017) or later, as =uiop:launch-program=\n   is used to run and communicate with python asyncronously.\n - [[https://common-lisp.net/project/trivial-garbage/][Trivial-garbage]], available through Quicklisp.\n - Python 2 or 3\n - (optional) The [[http://www.numpy.org/][NumPy]] python library for multidimensional arrays\n\nClone this repository into =~/quicklisp/local-projects/= or other\nlocation where it can be found by ASDF:\n#+BEGIN_SRC bash\n$ git clone https://github.com/bendudson/py4cl.git\n#+END_SRC\n\nthen load into Lisp with\n#+BEGIN_SRC lisp\n(ql:quickload :py4cl)\n#+END_SRC\n\n** Tests\n\nTests use [[https://github.com/tgutu/clunit][clunit]], and run on [[https://travis-ci.org/][Travis]] using [[https://github.com/luismbo/cl-travis][cl-travis]]. Most development\nis done under Arch linux with SBCL and Python3. To run the tests\nyourself:\n#+BEGIN_SRC lisp\n(asdf:test-system :py4cl)\n#+END_SRC\nor\n#+BEGIN_SRC lisp\n(ql:quickload :py4cl/tests)\n(py4cl/tests:run)\n#+END_SRC\n\n* Examples\n\nPy4CL allows python modules to be imported as Lisp packages, python\nfunctions to be called from lisp, and lisp functions called from\npython. In the example below, [[https://www.scipy.org/][SciPy]]'s [[https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.odeint.html][odeint]] function is used to\nintegrate ODEs defined by a Lisp function. The result is a Lisp array,\nwhich is then plotted using the [[https://matplotlib.org/][matplotlib]] plotting library.\n\n#+BEGIN_SRC lisp\n(ql:quickload :py4cl)\n\n(py4cl:import-module \"numpy\" :as \"np\")\n(py4cl:import-module \"scipy.integrate\" :as \"integrate\")\n\n;; Integrate some ODEs\n(defparameter *data*\n  (integrate:odeint \n   (lambda (y time) \n     (vector (aref y 1)       ; dy[0]/dt = y[1]\n             (- (aref y 0)))) ; dy[1]/dt = -y[0]\n   #(1.0 0.0)   ; Initial state\n   (np:linspace 0.0 (* 2 pi) 20)))  ; Vector of times\n\n; (array-dimensions *data*) =\u003e (20 2)\n\n;; Make a plot, save and show it in a window\n(py4cl:import-module \"matplotlib.pyplot\" :as \"plt\")\n\n(plt:plot *data*)\n(plt:xlabel \"Time\")\n(plt:savefig \"result.pdf\")\n(plt:show)\n#+END_SRC\n\nMore detailed examples of using python packages using =py4cl=:\n - [[./docs/numpy.org][Numpy arrays]]\n - [[./docs/matplotlib.org][Matplotlib plotting]]\n - [[./docs/scipy.org][Scipy scientific library]]\n\n* Reference\n** Direct evaluation of python code: =python-eval=, =python-exec=\n\nFor direct access to the python subprocess, =python-eval=\nevaluates an expression, converting the result to a suitable lisp\ntype. Note that there are nicer, more lispy wrappers around this function,\ndescribed below, but they are mostly built on top of =python-eval=.\n\n#+BEGIN_SRC lisp\n(asdf:load-system \"py4cl\")\n\n(py4cl:python-eval \"[i**2 for i in range(5)]\") ; =\u003e #(0 1 4 9 16)\n#+END_SRC\n\n#+RESULTS:\n| 0 | 1 | 4 | 9 | 16 |\n\n#+BEGIN_SRC lisp\n(py4cl:python-eval \"{'hello':'world', 'answer':42}\") ; =\u003e #\u003cHASH-TABLE :TEST EQUAL :COUNT 2\u003e\n#+END_SRC\n\n#+RESULTS:\n: #\u003cHASH-TABLE :TEST EQUAL :COUNT 2 {10036F03F3}\u003e\n\nData is passed between python and lisp as text. The python function\n=lispify= converts values to a form which can be read by the lisp\nreader; the lisp function =pythonize= outputs strings which can be\n=eval='d in python. The following type conversions are done:\n\n| Lisp type | Python type        |\n|-----------+--------------------|\n| NIL       | None               |\n| integer   | int                |\n| ratio     | fractions.Fraction |\n| real      | float              |\n| complex   | complex float      |\n| string    | str                |\n| hash map  | dict               |\n| list      | tuple              |\n| vector    | list               |\n| array     | NumPy array        |\n| symbol    | Symbol class       |\n| function  | function           |\n\nNote that python does not have all the numerical types which lisp has,\nfor example complex integers.\n\nBecause =python-eval= and =python-exec= evaluate strings as python\nexpressions, strings passed to them are not escaped or converted as\nother types are. To pass a string to python as an argument, call =py4cl::pythonize=\n\n#+BEGIN_SRC lisp\n(let ((my-str \"testing\"))\n  (py4cl:python-eval \"len(\" (py4cl::pythonize my-str) \")\" ))\n#+END_SRC\n\n#+RESULTS:\n: 7\n\nNote that this escaping is done automatically by higher-level interfaces like\n=python-call= and =chain=:\n#+BEGIN_SRC lisp\n(let ((my-str \"testing\"))\n  (py4cl:python-call \"len\" my-str))\n#+END_SRC\n\n#+RESULTS:\n: 7\n\n#+BEGIN_SRC lisp\n(let ((my-str \"testing\"))\n  (py4cl:chain (len my-str)))\n#+END_SRC\n\n#+RESULTS:\n: 7\n\nIf python objects cannot be converted into a lisp value, then they are\nstored and a handle is returned to lisp. This handle can be used to\nmanipulate the object, and when it is garbage collected the python\nobject is also deleted (using the [[https://common-lisp.net/project/trivial-garbage/][trivial-garbage]] package).\n\n#+BEGIN_SRC lisp\n(destructuring-bind (fig ax) (plt:subplots)\n  ;; fig is #S(PY4CL::PYTHON-OBJECT :TYPE \"\u003cclass 'matplotlib.figure.Figure'\u003e\" :HANDLE 6)\n  (py4cl:python-eval ax \".plot(\" #(0 1 0 1) \")\")\n  (plt:show)) \n#+END_SRC\n\nThe interface to python objects is nicer using =chain= (see below):\n#+BEGIN_SRC lisp\n(destructuring-bind (fig ax) (plt:subplots)\n  (py4cl:chain ax (plot #(0 1 0 1)))\n  (plt:show)) \n#+END_SRC\n\nThe python process can be explicitly started and stopped using\n=python-start= and =python-stop=, but =py4cl= functions start python\nautomatically if needed by calling =python-start-if-not-alive=.\n\n** Calling python functions: =python-call=\n\n=python-call= can be used to pass arguments to any python callable, \nsuch as a function in a module:\n\n#+BEGIN_SRC lisp\n(py4cl:python-exec \"import math\")\n(py4cl:python-call \"math.sqrt\" 42)\n#+END_SRC\n\n#+RESULTS:\n: 6.4807405\n\nor a lambda function:\n#+BEGIN_SRC lisp\n(py4cl:python-call \"lambda x: 2*x\" 21)\n#+END_SRC\n\n#+RESULTS:\n: 42\n\nKeywords are translated, with the symbol made lowercase:\n#+BEGIN_SRC lisp\n(py4cl:python-call \"lambda a=0, b=1: a-b\" :b 2 :a 1)\n#+END_SRC\n\n#+RESULTS:\n: -1\n\n** Calling python methods: =python-method=\n\nPython methods on objects can be called by using the =python-method= function. The first argument\nis the object (including strings, arrays, tuples); the second argument is either a string or a symbol\nspecifying the method, followed by any arguments:\n#+BEGIN_SRC lisp\n(py4cl:python-method \"hello {0}\" 'format \"world\") ; =\u003e \"hello world\"\n#+END_SRC\n\n#+RESULTS:\n: hello world\n\n#+BEGIN_SRC lisp\n(py4cl:python-method '(1 2 3) '__len__) ; =\u003e 3\n#+END_SRC\n\n#+RESULTS:\n: 3\n\n** Getting python attributes: =python-getattr=\n\nThe attributes of a python object can be accessed using the generic\nfuncton =python-getattr=, with the python object as first argument and\na string as the name of the attribute:\n#+BEGIN_SRC lisp\n(py4cl:python-getattr '(1 2 3) \"__doc__\")\n#+END_SRC\n\n\nNote: Methods for this function can also be defined for lisp classes,\nenabling python code to access attributes of lisp objects. See below\nfor details.\n\n** Chaining python methods: =chain=\n\nIn python it is quite common to apply a chain of method calls, data\nmember access, and indexing operations to an object. To make this work\nsmoothly in Lisp, there is the =chain= macro (Thanks to @kat-co and\n[[https://common-lisp.net/project/parenscript/reference.html][parenscript]] for the inspiration). This consists of a target object,\nfollowed by a chain of operations to apply.  For example\n#+BEGIN_SRC lisp\n(py4cl:chain \"hello {0}\" (format \"world\") (capitalize)) ; =\u003e \"Hello world\"\n#+END_SRC\n\n#+RESULTS:\n: Hello world\n\nwhich is converted to python \n#+BEGIN_SRC python\nreturn \"hello {0}\".format(\"world\").capitalize()\n#+END_SRC\n\n#+RESULTS:\n: Hello world\n\nThe only things which are treated specially by this macro are lists\nand symbols at the top level. The first element of lists are treated as\npython method names, top-level symbols are treated as data\nmembers. Everything else is evaluated as lisp before being converted\nto a python value.\n\nIf the first argument is a list, then it is assumed to be a python\nfunction to be called; otherwise it is evaluated before converting to\na python value. For example\n#+BEGIN_SRC lisp\n(py4cl:chain (slice 3) stop)\n#+END_SRC\n\n#+RESULTS:\n: 3\n\nis converted to the python:\n#+BEGIN_SRC python\nreturn slice(3).stop\n#+END_SRC\n\n#+RESULTS:\n: 3\n\nSymbols as first argument, or arguments to python methods, are\nevaluated, so the following works:\n#+BEGIN_SRC lisp\n(let ((format-str \"hello {0}\")\n      (argument \"world\"))\n (py4cl:chain format-str (format argument))) ; =\u003e \"hello world\"\n#+END_SRC\n\n#+RESULTS:\n: hello world\n\nArguments to methods are lisp, since only the top level forms in =chain= are treated specially:\n#+BEGIN_SRC lisp\n(py4cl:chain \"result: {0}\" (format (+ 1 2))) ; =\u003e \"result: 3\"\n#+END_SRC\n\n#+RESULTS:\n: result: 3\n\nIndexing with =[]= brackets is commonly used in python, which calls the =__getitem__= method.\nThis method can be called like any other method\n#+BEGIN_SRC lisp\n(py4cl:chain \"hello\" (__getitem__ 4)) ; =\u003e \"o\"\n#+END_SRC\n\n#+RESULTS:\n: o\n\nbut since this is a common method an alias =[]= is supported:\n#+BEGIN_SRC lisp\n(py4cl:chain \"hello\" ([] 4)) ; =\u003e \"o\"\n#+END_SRC\n\n#+RESULTS:\n: o\n\nwhich is converted to the python\n#+BEGIN_SRC python\nreturn \"hello\"[4]\n#+END_SRC\n\n#+RESULTS:\n: o\n\nFor simple cases where the index is a value like a number or string\n(not a symbol or a list), the brackets can be omitted:\n#+BEGIN_SRC lisp\n(py4cl:chain \"hello\" 4) ; =\u003e \"o\"\n#+END_SRC\n\n#+RESULTS:\n: o\n\nSlicing can be done by calling the python =slice= function:\n#+BEGIN_SRC lisp\n(py4cl:chain \"hello\" ([] (py4cl:python-call \"slice\" 2 4)))  ; =\u003e \"ll\"\n#+END_SRC\n\n#+RESULTS:\n: ll\n\nwhich could be imported as a lisp function (see below):\n#+BEGIN_SRC lisp\n(py4cl:import-function \"slice\")\n(py4cl:chain \"hello\" ([] (slice 2 4))) ; =\u003e \"ll\"\n#+END_SRC\n\n#+RESULTS:\n: ll\n\nThis of course also works with multidimensional arrays:\n#+BEGIN_SRC lisp\n(py4cl:chain #2A((1 2 3) (4 5 6))  ([] 1 (slice 0 2)))  ;=\u003e #(4 5)\n#+END_SRC\n\n#+RESULTS:\n| 4 | 5 |\n\nSometimes the python functions or methods may contain upper case\ncharacters; class names often start with a capital letter. All symbols\nare converted to lower case, but the case can be controlled by passing\na string rather than a symbol as the first element:\n#+BEGIN_SRC lisp\n;; Define a class\n(py4cl:python-exec\n   \"class TestClass:\n      def doThing(self, value = 42):\n        return value\")\n\n;; Create an object and call the method\n(py4cl:chain (\"TestClass\") (\"doThing\" :value 31))  ; =\u003e 31\n#+END_SRC\nNote that the keyword is converted, converting to lower case.\n\n** Printing from python\n\nSince standard output is used for communication between lisp and python, this is\nredirected (to a =StringIO= buffer) while user python code is running. The\noutput from python functions is then sent to lisp, to be printed to\n=*standard-output*=.  This means that anything printed by the python process may\nonly appear in chunks, as it is sent to lisp. The following does however work as\nexpected:\n#+BEGIN_SRC lisp :results output\n(py4cl:chain (print \"hello world\")) \n; =\u003e prints \"hello world\", returns NIL\n#+END_SRC\n\n#+RESULTS:\n: hello world\n\nIn python =print_function= is imported from =__future__=, so should be available\nas a function in python 2.6+, as well as in version 3+.\n\n** Asynchronous python functions: =python-call-async=\n\nOne of the advantages of using streams to communicate with a separate\npython process, is that the python and lisp processes can run at the\nsame time. =python-call-async= calls python but returns a closure\nimmediately. The python process continues running, and the result can\nbe retrieved by calling the returned closure. \n\n#+BEGIN_SRC lisp\n(defparameter thunk (py4cl:python-call-async \"lambda x: 2*x\" 21))\n\n(funcall thunk)  ; =\u003e 42\n#+END_SRC\n\n#+RESULTS:\n: 42\n\nIf the function call requires callbacks to lisp, then these will only\nbe serviced when a =py4cl= function is called. In that case the python\nfunction may not be able to finish until the thunk is called. This\nshould not result in deadlocks, because all =py4cl= functions can\nservice callbacks while waiting for a result.\n\n** Importing functions: =import-function=\n\nPython functions can be made available in Lisp by using =import-function=. By\ndefault this makes a function which can take any number of arguments, and then\ntranslates these into a call to the python function.\n#+BEGIN_SRC lisp\n(asdf:load-system \"py4cl\")\n\n(py4cl:python-exec \"import math\")\n(py4cl:import-function \"math.sqrt\")\n(math.sqrt 42) ; =\u003e 6.4807405\n#+END_SRC\n\n#+RESULTS:\n: 6.4807405\n\nIf a different symbol is needed in Lisp then the =:as= keyword can be\nused with either a string or symbol:\n#+BEGIN_SRC lisp\n(py4cl:import-function \"sum\" :as \"pysum\")\n(pysum '(1 2 3))  ; =\u003e 6\n#+END_SRC\n\n#+RESULTS:\n: 6\n\nThis is implemented as a macro which defines a function which in turn calls =python-call=.\n\n** Importing modules: =import-module=\n\nPython modules can be imported as lisp packages using =import-module=.\nFor example, to import the [[https://matplotlib.org/][matplotlib]] plotting library, and make its functions\navailable in the package =PLT= from within Lisp:\n#+BEGIN_SRC lisp :session import-example\n(asdf:load-system \"py4cl\")\n(py4cl:import-module \"matplotlib.pyplot\" :as \"plt\") ; Creates PLT package\n#+END_SRC\n\n#+RESULTS:\n: T\n\nThis will also import it into the python process as the module =plt=, so that\n=python-call= or =python-eval= can also make use of the =plt= module. \n\nLike =python-exec=, =python-call= and other similar functions, \n=import-module= starts python if it is not already running, so that\nthe available functions can be discovered.\n\nThe python docstrings are made available as Lisp function docstrings, so we can see them\nusing =describe=:\n#+BEGIN_SRC  lisp :session import-example\n(describe 'plt:plot)\n#+END_SRC\n\nFunctions in the =PLT= package can be used to make simple plots:\n#+BEGIN_SRC lisp :session import-example\n(plt:plot #(1 2 3 2 1) :color \"r\")\n(plt:show)\n#+END_SRC\n\n#+RESULTS:\n: NIL\n\nNotes:\n -  =import-module= should be used as a top-level form, to ensure that\n   the package is defined before it is used.\n\n- If using =import-module= within [[https://orgmode.org/worg/org-contrib/babel/][org-mode babel]] then the import\n  should be done in a separate code block to the first use of the\n  imported package, or a condition will be raised like \"Package NP\n  does not exist.\"\n\n** Exporting a function to python: =export-function=\n\nLisp functions can be passed as arguments to =python-call= \nor imported functions:\n#+BEGIN_SRC lisp\n(py4cl:python-exec \"from scipy.integrate import romberg\")\n\n(py4cl:python-call \"romberg\" \n                   (lambda (x) (/ (exp (- (* x x)))\n                                  (sqrt pi)))\n                   0.0 1.0) ; Range of integration\n#+END_SRC\n\n#+RESULTS:\n: 0.4213504\n\nLisp functions can be made available to python code using =export-function=:\n#+BEGIN_SRC lisp\n(py4cl:python-exec \"from scipy.integrate import romberg\")\n\n(py4cl:export-function (lambda (x) (/ (exp (- (* x x)))\n                                      (sqrt pi))) \"gaussian\")\n\n(py4cl:python-eval \"romberg(gaussian, 0.0, 1.0)\") ; =\u003e 0.4213504\n#+END_SRC\n\n#+RESULTS:\n: 0.4213504\n\n** Manipulating objects remotely: =remote-objects=\n\nIf a sequence of python functions and methods are being used to manipulate data,\nthen data may be passed between python and lisp. This is fine for small amounts\nof data, but inefficient for large datasets.\n\nThe =remote-objects= and =remote-objects*= macros provide =unwind-protect= environments\nin which all python functions return handles rather than values to lisp. This enables\npython functions to be combined without transferring much data.\n\nThe difference between these macros is =remote-objects= returns a handle, but\n=remote-objects*= evaluates the result, and so will return a value if possible.\n\n#+BEGIN_SRC lisp\n(py4cl:remote-objects (py4cl:python-eval \"1+2\")) ; =\u003e #S(PY4CL::PYTHON-OBJECT :TYPE \"\u003cclass 'int'\u003e\" :HANDLE 0)\n#+END_SRC\n\n#+RESULTS:\n: #S(PY4CL::PYTHON-OBJECT :TYPE \"\u003cclass 'int'\u003e\" :HANDLE 4)\n\n#+BEGIN_SRC lisp\n(py4cl:remote-objects* (py4cl:python-eval \"1+2\")) ; =\u003e 3\n#+END_SRC\n\n#+RESULTS:\n: 3\n\nThe advantage comes when dealing with large arrays or other datasets:\n#+BEGIN_SRC lisp\n(time (np:sum (np:arange 1000000)))\n; =\u003e 3.672 seconds of real time\n;    390,958,896 bytes consed\n#+END_SRC\n\n#+BEGIN_SRC lisp\n(time (py4cl:remote-objects* (np:sum (np:arange 1000000))))\n; =\u003e 0.025 seconds of real time\n;    32,544 bytes consed\n#+END_SRC\n** =setf=-able places\n\nThe =python-eval= function is =setf=-able, so that python objects can\nbe assigned to by using =setf=. Since =chain= uses =python-eval=, it is also\n=setf=-able. This can be used to set elements in an array, entries in a dict/hash-table, \nor object data members, for example:\n#+BEGIN_SRC lisp\n(py4cl:import-module \"numpy\" :as \"np\")\n#+END_SRC\n\n#+RESULTS:\n: T\n\n#+BEGIN_SRC lisp\n(py4cl:remote-objects*\n  (let ((array (np:zeros '(2 2))))\n    (setf (py4cl:chain array ([] 0 1)) 1.0\n          (py4cl:chain array ([] 1 0)) -1.0)\n    array)) \n; =\u003e #2A((0.0 1.0)\n;        (-1.0 0.0))\n#+END_SRC\n\n#+RESULTS:\n: #2A((0.0 1.0) (-1.0 0.0))\n\nNote that this modifies the value in python, so the above example only\nworks because =array= is a handle to a python object, rather than an\narray which is stored in lisp. The following therefore does not work:\n#+BEGIN_SRC lisp\n(let ((array (np:zeros '(2 2))))\n  (setf (py4cl:chain array ([] 0 1)) 1.0\n        (py4cl:chain array ([] 1 0)) -1.0)\n  array)\n; =\u003e #2A((0.0 0.0)\n;        (0.0 0.0))\n#+END_SRC\n\n#+RESULTS:\n: #2A((0.0 0.0) (0.0 0.0))\n\nThe =np:zeros= function returned an array to lisp; the array was then\nsent to python and modified in python. The modified array is not\nreturned, since this would mean transferring the whole array. If the\nvalue is in lisp then just use the lisp functions:\n#+BEGIN_SRC lisp\n(let ((array (np:zeros '(2 2))))\n  (setf (aref array 0 1) 1.0\n        (aref array 1 0) -1.0)\n  array)\n; =\u003e #2A((0.0 1.0)\n;        (-1.0 0.0))\n#+END_SRC\n\n#+RESULTS:\n: #2A((0.0 1.0) (-1.0 0.0))\n\n** Passing lisp objects to python: =python-getattr=\n\nLisp structs and class objects can be passed to python, put into data structures and\nreturned:\n#+BEGIN_SRC lisp\n(py4cl:import-function \"dict\") ; Makes python dictionaries\n\n(defstruct test-struct \n    x y)\n\n(let ((map (dict :key (make-test-struct :x 1 :y 2))))  ; Make a dictionary, return as hash-map\n  ;; Get the struct from the hash-map, and get the Y slot\n  (test-struct-y\n    (py4cl:chain map \"key\")))  ; =\u003e 2\n#+END_SRC\n\n#+RESULTS:\n: 2\n\nIn python this is handled using an object of class =UnknownLispObject=, which\ncontains a handle. The lisp object is stored in a hash map\n=*lisp-objects*=. When the python object is deleted, a message is sent to remove\nthe object from the hash map.\n\nTo enable python to access slots, or call methods on a struct or class, a\nhandler function needs to be registered. This is done by providing a method \nfor generic function =python-getattr=. This function will be called when a\npython function attempts to access attributes of an object (=__getattr__=\nmethod).\n\n#+BEGIN_SRC lisp\n;; Define a class with some slots\n(defclass test-class ()\n  ((value :initarg :value)))\n\n;; Define a method to handle calls from python\n(defmethod py4cl:python-getattr ((object test-class) slot-name)\n  (cond\n    ((string= slot-name \"value\") ; data member\n      (slot-value object 'value))\n    ((string= slot-name \"func\")  ; method, return a function\n      (lambda (arg) (* 2 arg)))\n    (t (call-next-method)))) ; Otherwise go to next method\n\n(let ((instance (make-instance 'test-class :value 21))) \n  ;; Get the value from the slot, call the method\n  ;; python: instance.func(instance.value)\n  (py4cl:chain instance (func (py4cl:chain instance value))))  ; =\u003e 42\n#+END_SRC\n\n#+RESULTS:\n: 42\n\nInheritance then works as usual with CLOS methods:\n#+BEGIN_SRC lisp\n;; Class inheriting from test-class\n(defclass child-class (test-class)\n  ((other :initarg :other)))\n\n;; Define method which passes to the next method if slot not recognised\n(defmethod py4cl:python-getattr ((object child-class) slot-name)\n  (cond\n    ((string= slot-name \"other\")\n     (slot-value object 'other))\n    (t (call-next-method))))\n\n(let ((object (make-instance 'child-class :value 42 :other 3)))\n  (list \n    (py4cl:chain object value) ; Call TEST-CLASS getattr method via CALL-NEXT-METHOD\n    (py4cl:chain object other))) ;=\u003e (42 3)\n#+END_SRC\n\n#+RESULTS:\n| 42 | 3 |\n","funding_links":[],"categories":["Python ##"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbendudson%2Fpy4cl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbendudson%2Fpy4cl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbendudson%2Fpy4cl/lists"}