{"id":13804008,"url":"https://github.com/digikar99/py4cl2-cffi","last_synced_at":"2026-01-12T02:56:31.770Z","repository":{"id":113517213,"uuid":"579947773","full_name":"digikar99/py4cl2-cffi","owner":"digikar99","description":"CFFI based alternative to py4cl2","archived":false,"fork":false,"pushed_at":"2024-07-26T18:34:06.000Z","size":582,"stargazers_count":37,"open_issues_count":8,"forks_count":9,"subscribers_count":7,"default_branch":"master","last_synced_at":"2024-08-04T01:03:04.858Z","etag":null,"topics":[],"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":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/digikar99.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2022-12-19T10:43:44.000Z","updated_at":"2024-08-04T01:03:06.665Z","dependencies_parsed_at":"2023-11-16T07:25:40.147Z","dependency_job_id":"d3df8189-248e-446e-ba0e-ff296a958979","html_url":"https://github.com/digikar99/py4cl2-cffi","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digikar99%2Fpy4cl2-cffi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digikar99%2Fpy4cl2-cffi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digikar99%2Fpy4cl2-cffi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/digikar99%2Fpy4cl2-cffi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/digikar99","download_url":"https://codeload.github.com/digikar99/py4cl2-cffi/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225247816,"owners_count":17444118,"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-08-04T01:00:40.193Z","updated_at":"2026-01-12T02:56:31.762Z","avatar_url":"https://github.com/digikar99.png","language":"Common Lisp","funding_links":[],"categories":["Python ##"],"sub_categories":[],"readme":"py4cl2-cffi - a CFFI approach to python interfacing in Common Lisp\n---\n\n\u003e Despite being featureful, I won't recommend using py4cl2-cffi in production or in long-term projects *yet*.\n\nPrevious Common Lisp attempts:\n- [burgled-batteries3](https://github.com/snmsts/burgled-batteries3)\n- [cl-python](https://github.com/metawilm/cl-python).\n\nCommon Lisp / SBCL attempt with support for calling Lisp from Python: [lang](https://github.com/marcoheisig/lang).\n\nOther language approaches\n- see [this reddit thread](https://www.reddit.com/r/lisp/comments/yuipy7/pyffi_use_python_from_racket/) for PyFFI in racket, as well as Gambit Scheme\n- [PyCall in Julia](https://github.com/JuliaPy/PyCall.jl)\n\nSee [this publication](https://zenodo.org/records/10997435) for the broad design.\n\n# Table of Contents\n\n\u003c!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc --\u003e\n**Table of Contents**\n\n- [py4cl2-cffi - a CFFI approach to python interfacing in Common Lisp](#py4cl2-cffi---a-cffi-approach-to-python-interfacing-in-common-lisp)\n- [Table of Contents](#table-of-contents)\n- [Why](#why)\n- [Limitations](#limitations)\n- [Caveats](#caveats)\n- [Status](#status)\n- [Tutorial](#tutorial)\n    - [Installation](#installation)\n        - [Lisp Package Managers](#lisp-package-managers)\n        - [Barebones + quicklisp](#barebones--quicklisp)\n    - [Configuration](#configuration)\n    - [Basic Usage](#basic-usage)\n    - [Passing arrays by reference:](#passing-arrays-by-reference)\n    - [Callbacks](#callbacks)\n    - [Numpy](#numpy)\n    - [Plotting](#plotting)\n    - [Optimization](#optimization)\n- [Developer Thoughts on Garbage Collection](#developer-thoughts-on-garbage-collection)\n- [API Reference](#api-reference)\n- [py4cl2-cffi](#py4cl2-cffi)\n    - [\\*defpymodule-silent-p\\*](#defpymodule-silent-p)\n    - [\\*internal-features\\*](#internal-features)\n    - [\\*lispifiers\\*](#lispifiers)\n    - [\\*print-pyobject\\*](#print-pyobject)\n    - [\\*print-pyobject-wrapper-identity\\*](#print-pyobject-wrapper-identity)\n    - [\\*pygc-threshold\\*](#pygc-threshold)\n    - [\\*pythonizers\\*](#pythonizers)\n    - [+disable-pystop+](#disable-pystop)\n    - [+py-empty-tuple+](#py-empty-tuple)\n    - [+py-empty-tuple-pointer+](#py-empty-tuple-pointer)\n    - [+py-none+](#py-none)\n    - [+py-none-pointer+](#py-none-pointer)\n    - [+python-call-mode+](#python-call-mode)\n    - [chain](#chain)\n    - [chain\\*](#chain)\n    - [define-lispifier](#define-lispifier)\n    - [defpyfun](#defpyfun)\n    - [defpymodule](#defpymodule)\n    - [disable-pygc](#disable-pygc)\n    - [enable-pygc](#enable-pygc)\n    - [export-function](#export-function)\n    - [import-function](#import-function)\n    - [import-module](#import-module)\n    - [pycall](#pycall)\n    - [pyerror](#pyerror)\n    - [pyeval](#pyeval)\n    - [pyexec](#pyexec)\n    - [pyfor](#pyfor)\n    - [pygenerator](#pygenerator)\n    - [pyhelp](#pyhelp)\n    - [pymethod](#pymethod)\n    - [pymethod-list](#pymethod-list)\n    - [pyobject-wrapper](#pyobject-wrapper)\n    - [pyobject-wrapper-eq](#pyobject-wrapper-eq)\n    - [pyobject-wrapper-eq\\*](#pyobject-wrapper-eq)\n    - [pyref](#pyref)\n    - [pyslot-list](#pyslot-list)\n    - [pyslot-value](#pyslot-value)\n    - [pystart](#pystart)\n    - [pystop](#pystop)\n    - [python-alive-p](#python-alive-p)\n    - [python-getattr](#python-getattr)\n    - [python-setattr](#python-setattr)\n    - [python-start-if-not-alive](#python-start-if-not-alive)\n    - [pythonize](#pythonize)\n    - [pyvalue](#pyvalue)\n    - [pyversion-info](#pyversion-info)\n    - [raw-pyeval](#raw-pyeval)\n    - [raw-pyexec](#raw-pyexec)\n    - [simple-pyerror](#simple-pyerror)\n    - [with-lispifiers](#with-lispifiers)\n    - [with-pygc](#with-pygc)\n    - [with-python-error-output](#with-python-error-output)\n    - [with-python-gil](#with-python-gil)\n    - [with-python-output](#with-python-output)\n    - [with-pythonizers](#with-pythonizers)\n    - [with-remote-objects](#with-remote-objects)\n    - [with-remote-objects\\*](#with-remote-objects)\n\n\u003c!-- markdown-toc end --\u003e\n\n# Why\n\n[py4cl2](https://github.com/digikar99/py4cl2) has gotten the work done for the past few years. But it has the overhead of (i) stream-based inter-process-communication (ii) eval. That's as worse as one could get.\n\nHowever, when capable, the CFFI approach can be a 50 times faster than py4cl2.\n\n```lisp\nCL-USER\u003e (py4cl2-cffi:raw-pyexec \"def foo(): return str(1)\")\n0\nCL-USER\u003e (time (dotimes (i 10000)\n                 (py4cl2-cffi:pycall \"foo\")))\nEvaluation took:\n  0.080 seconds of real time\n  0.079740 seconds of total run time (0.079740 user, 0.000000 system)\n  100.00% CPU\n  174,443,654 processor cycles\n  3,045,440 bytes consed\n\nNIL\nCL-USER\u003e (py4cl2:raw-pyexec \"def foo(): return str(1)\")\n; No value\nCL-USER\u003e (time (dotimes (i 10000)\n                 (py4cl2:pycall \"foo\")))\nEvaluation took:\n  1.051 seconds of real time\n  0.482699 seconds of total run time (0.304186 user, 0.178513 system)\n  45.96% CPU\n  20,000 forms interpreted\n  2,327,364,348 processor cycles\n  5,760,832 bytes consed\n\nNIL\n```\n\n# Limitations\n\nGoals are less ambitious than burgled-batteries. We aim to get \"most\" libraries working, with a special focus on functional python.\n- Only specialized arrays can be passed by reference. Other values will be passed by value.\n- The goal is getting the functional aspects of python - those python functions that do not modify their inputs should \"work\". Non-functional python functions can only work with arrays. Other functions that modify their inputs will not work.\n\nTested only on Ubuntu 20.04 (CI) and Ubuntu 18.04 (personal machine). Porting to Windows does not look trivial, but someone could prove me wrong (at least provide some pointers!).\n\n# Caveats\n\nUnlike `py4cl` and `py4cl2`, `py4cl2-cffi` can only use one python version in a running lisp image. In addition, while the author has been successful in running the [py4cl2-cffi-tests](https://github.com/digikar99/py4cl2-cffi-tests) without segmentation faults, the project is still in beta stage, so be prepared to run into segmentation faults especially while importing and using python modules.\n\nThe project is being tested on\n\n- SBCL, CCL, ECL for Linux on Github Actions.\n- SBCL for MacOS 13 / amd64\n- SBCL for MacOS 15 / arm64 (M* macs)\n\n# Status\n\n- [x] garbage collection touches\n    - An effort has been made to keep track of reference counts; but if something is missed, and users notice a memory leak, feel free to [raise an issue](https://github.com/digikar99/py4cl2/issues/new)!\n    - trivial-garbage:finalize is used to establish the decref process for the pointer corresponding to the pyobject. However, this requires holding the GIL, and so, the user might need to evaluate `(py4cl2-cffi::pygil-release)` at the top level to release the GIL of the current thread, so that the finalizer thread can then acquire it.\n- [x] documentation: see the docstrings for the moment; these need to be collected into a more user-friendly reference along with a couple of other things.\n- [x] function return-values\n- [x] function arguments\n- [x] integers\n- [x] strings with SBCL/Unicode\n- [x] tuples\n- [x] lists\n- [x] dicts\n- [x] double floats\n- [x] numpy arrays to CL arrays\n- [x] output (partially)\n- [x] error output (partially)\n- [x] python variable values\n- [x] object slots\n- [x] methods\n- [x] python stdout to lisp stdout (asynchronous, make sure to `sys.stdout.flush()`)\n- [x] `with-python-output`\n- [x] lisp callbacks\n- [x] numpy arrays to non-CL arrays\n- [x] arbitrary module import\n- [x] numpy floats\n- [x] optimization (See [./perf-compare/README.org](./perf-compare/README.org).)\n- [ ] unloading python libraries to allow reloading python without restarting lisp (?)\n- [ ] playing nice with dumping a lisp image\n- [x] dedicated threaded mode: some python libraries (including matplotlib) hate multithreaded environments\n\n... and much more ...\n\n# Tutorial\n\n## Installation\n\n### Lisp Package Managers\n\nUse [ultralisp](https://ultralisp.org/) or [ocicl](https://github.com/ocicl/ocicl).\n\n### Barebones + quicklisp\n\nLocally clone the repository and quickload against the latest quicklisp dist.\n\n```\ngit clone https://github.com/digikar99/py4cl2-cffi\n```\n\n\n## Configuration\n\n```lisp\nCL-USER\u003e (ql:quickload \"py4cl2-cffi/config\")\nTo load \"py4cl2-cffi/config\":\n  Load 1 ASDF system:\n    py4cl2-cffi/config\n; Loading \"py4cl2-cffi/config\"\n[package py4cl2-cffi/config]\n(\"py4cl2-cffi/config\")\n```\n\nLoading `py4cl2-cffi/config` sets the following variables of interest in the `py4cl2-cffi/config` package:\n\n- `*python-ldflags*`\n- `*python-includes*`\n- `*python-executable-path*`\n- `*python-site-packages-path*`\n\nIn addition, `py4cl2-cffi/config` also exports the following useful symbols:\n\n- `print-configuration`: It is fbound to a function which prints the ldflags and includes that will be used for the compilation of the utility shared object/library that bridges the python C-API with lisp.\n- `shared-library-from-lflag`: This is fbound to a generic function which takes in two arguments. The first argument is an ldflag (like `-lpython3.10`) and the second argument is the `(uiop:operating-system)` as a keyword to be used for specialization on the users systems. Each method should return the shared library name associated with that ldflag and software type. For example, when `(uiop:operating-system)` is `:linux`, the relevant method should return `python3.10.so`.\n\nFor the most part, configuration happens automatically while loading `py4cl2-cffi/config`. This requires that `python3` and `python3-config` point to the right programs in the environment in which the lisp is run. Configuration in global or conda environments should be automatic then.\n\nHowever, pyvenv environments require that users set `*python-executable-path*` manually, before loading `py4cl2-cffi`.\n\n## Basic Usage\n\n```lisp\n(defpackage :python-lisp-user\n  (:use :cl :py4cl2-cffi))\n\n(in-package :python-lisp-user)\n```\n\nStart the embedded python by `(pystart)`. If successful, `(python-alive-p)` should return `T`.\n\nAt the heart of py4cl2-cffi are [pyvalue](#pyvalue) and [pycall](#pycall). This is in contrast to py4cl2. In py4cl2, [raw-pyeval](#raw-pyeval) and [raw-pyexec](#raw-pyexec) were the heart. There, pycall ultimately resulted in calls to raw-pyeval. However, py4cl2-cffi has the opposite behavior. Here, raw-pyeval and raw-pyexec ultimately depend on pyvalue and pycall. raw-pyeval and raw-pyexec are not only slow but also ugly in their implementations. Thus, whereever possible, you should try to use pyvalue, (setf pyvalue), and pycall.\n\n`pyvalue` lets you obtain the value corresponding to a python name. It takes a string denoting a python name and returns a value corresponding to that name. For example,\n\n```lisp\n(pyvalue \"sys\")\n;=\u003e #\u003cPYOBJECT-WRAPPER :type \u003cclass 'module'\u003e\n;     \u003cmodule 'sys' (built-in)\u003e\n;    {1004AAA1C3}\u003e\n```\n\nAn attempt will be made to convert the return values to lisp objects.\n\n```lisp\n(pyvalue \"answer_to_everything\")\n; Evaluation aborted on #\u003cPY4CL2-CFFI:PYERROR {100514C153}\u003e.\n(setf (pyvalue \"answer_to_everything\") 42)\n;=\u003e 42\n(pyvalue \"answer_to_everything\")\n;=\u003e 42\n```\n\nBut if such a conversion is not possible (see [define-lispifier](#define-lispifiers) and [pythonize](#pythonize)), an instance of [pyobject-wrapper](#pyobject-wrapper) will be returned. Note that pyvalue also has a `(setf pyvalue)` which can be used to set the value of a python name, as we did in the above example.\n\nThe printing of a `pyobject-wrapper`-instance can be made less ugly by setting [\\*print-pyobject-wrapper-identity\\*](#print-pyobject-wrapper-identity) to NIL.\n\n```lisp\n(pyvalue \"sys\")\n;=\u003e #\u003cPYOBJECT-WRAPPER :type \u003cclass 'module'\u003e\n;    \u003cmodule 'sys' (built-in)\u003e\n;   {1004AAA1C3}\u003e\n(setf *print-pyobject-wrapper-identity* nil)\n;=\u003e NIL\n(pyvalue \"sys\")\n;=\u003e \u003cmodule 'sys' (built-in)\u003e\n```\n\nA python callable, that is, a python function, class, or anything that can be called, can be called using [pycall](#pycall). The first argument of `pycall` is usually the name of a python callable.\n\n```lisp\n(pyvalue \"str\")\n;=\u003e \u003cclass 'str'\u003e\n(pycall \"str\" 42)\n;=\u003e \"42\"\n```\n\nBut it can also be\n- a `cffi:foreign-pointer` to the python callable,\n- a `pyobject-wrapper`-instance wrapping the foreign pointer,\n- a string, which on passing to [raw-pyeval](#raw-pyeval) returns a python callable\n- or, any object that can be [pythonize](#pythonize)-d to a python callable.\n\nLike [pyvalue](#pyvalue), `pycall` too will attempt to convert the return value to a lisp object. But if that is not possible, it will return a [pyobject-wrapper](#pyobject-wrapper)-instance.\n\nPython modules can be imported using the lightweight [import-module](#import-module). The values from the module can then be accessed using pyvalue, and callables can be called using pycall.\n\n```lisp\n(import-module \"math\") ;=\u003e T\n(pyvalue \"math.pi\") ;=\u003e 3.141592653589793d0\n(pycall \"math.hypot\" 3 4) ;=\u003e 5.0d0\n```\n\nA particular name from a particular python module can be imported using [import-function](#import-function).\n\n```lisp\n(import-function \"hypot\" \"math\") ;=\u003e T\n(pycall \"hypot\" 12 5) ;=\u003e 13.0d0\n```\n\n[defpyfun](#defpyfun) and [defpymodule](#defpymodule) are heavyweight alternatives to import-function and import-module. defpyfun defines a lisp function that calls the python callable. defpymodule defines lisp package(s) that holds certain symbols. These symbols are fbound to lisp functions which call corresponding python callables. For example, the below `(defpymodule \"math\")` form defines a lisp-package with name `MATH`.\n\n```lisp\n(defpymodule \"math\")\n; Defining MATH for accessing python package math..\n;=\u003e T\n```\nThe lisp package `MATH` contains lisp functions that call corresponding python callables. For example the lisp function `math:hypot` below calls the python callable `math.hypot`.\n\n```lisp\n(math:hypot 7 24) ;=\u003e 25.0d0\n```\n\n## Passing arrays by reference:\n\n```lisp\nPYTHON-LISP-USER\u003e (ql:quickload \"array-operations\")\nTo load \"array-operations\":\n  Load 1 ASDF system:\n    array-operations\n; Loading \"array-operations\"\n\n(\"array-operations\")\nPYTHON-LISP-USER\u003e (let ((a (aops:rand* 'single-float 10))\n                        (b (aops:rand* 'single-float 10)))\n                    (print a)\n                    (print b)\n                    (pycall \"numpy.add\" a b :out a)\n                    a)\n\n#(0.5093733 0.615062 0.5520501 0.4115485 0.35940528 0.0056368113 0.31019592\n  0.4214077 0.32522345 0.2879219)\n#(0.23799527 0.9120656 0.99672806 0.54783416 0.91948783 0.14750922 0.68077135\n  0.75351477 0.17053545 0.6163509)\n#(0.7473686 1.5271276 1.5487782 0.95938265 1.2788931 0.15314603 0.9909673\n1.1749225 0.4957589 0.9042728)\n\nPYTHON-LISP-USER\u003e (let ((a (aops:rand* 'double-float '(3 3))))\n                    (print a)\n                    (pycall \"numpy.linalg.svd\" a))\n#2A((0.8441753387451172d0 0.3109557628631592d0 0.34773027896881104d0)\n    (0.3423733711242676d0 0.6038261651992798d0 0.41209208965301514d0)\n    (0.5945597887039185d0 0.06366562843322754d0 0.6331008672714233d0))\nSVDResult(U=array([[-0.65892971,  0.44979069, -0.60290959],\n       [-0.65302311, -0.73986678,  0.16173425],\n       [-0.37332622,  0.50028539,  0.78124392]]), S=array([1.41737039, 0.5804025 , 0.01089903]), Vh=array([[-0.53068899, -0.75714703, -0.38091676],\n       [-0.6309116 ,  0.65299712, -0.41898128],\n       [ 0.56596798,  0.01797605, -0.82423122]]))\n```\n\n## Callbacks\n\n```lisp\nPYTHON-LISP-USER\u003e (raw-pyexec \"def foo(fn, *args, **kwargs): return fn(*args, **kwargs)\")\n\n; No value\nPYTHON-LISP-USER\u003e (pycall \"foo\" (lambda (d e \u0026rest args \u0026key a b \u0026allow-other-keys)\n                                  (declare (ignore a b))\n                                  (list* d e args))\n                          8 9 :a 2 :b 3 :d 5)\n(8 9 \"d\" 5 \"b\" 3 \"a\" 2)\n```\n\n## Numpy\n\n```lisp\nPYTHON-LISP-USER\u003e (defpymodule \"numpy\" t :silent t)\nT\nPYTHON-LISP-USER\u003e (numpy.random:random '(2 3 4))\n#3A(((0.9556724994386294d0 0.9207667929741092d0 0.38080996781642207d0\n      0.36058417847643864d0)\n     (0.1939761803809288d0 0.052707969761970785d0 0.5641774015926598d0\n      0.34218703751890367d0)\n     (0.663085466238284d0 0.8208948328437302d0 0.768715035806218d0\n      0.8225795094037658d0))\n    ((0.9523448513613038d0 0.8293149376922084d0 0.6616993552816121d0\n      0.560839589292125d0)\n     (0.004265522613073891d0 0.8874616779694773d0 0.45500882951834853d0\n      0.34081255137211874d0)\n     (0.3041085477740366d0 0.4351811902627044d0 0.031589664841209175d0\n      0.6375274178283377d0)))\nPYTHON-LISP-USER\u003e (numpy:sum * :axis '(0 2))\n#(5.622032172332849d0 2.8405971707274817d0 4.483681664998286d0)\nPYTHON-LISP-USER\u003e (with-lispifiers ((array (lambda (o)\n                                             (magicl:from-array o (array-dimensions o)))))\n                    (numpy.random:random '(3 4)))\n#\u003cMAGICL:MATRIX/DOUBLE-FLOAT (3x4):\n   0.814     0.278     0.330     0.782\n   0.858     0.342     0.282     0.225\n   0.806     0.144     0.543     0.215\u003e\n```\n\n## Plotting\n\nIf you are using Emacs/SLIME or any other multithreaded lisp development environment, you may run into segmentation faults while using matplotlib. This is because matplotlib expects itself from the same main thread that initiated the (embedded) python. There are two options to solve this problem (see [this discussion](https://github.com/digikar99/py4cl2-cffi/issues/11)):\n\n1. Set `(setf swank:*communication-style* nil)` in `swank.lisp`\n2. Or, `(setf py4cl2-cffi/config:*python-call-mode* :dedicated-thread)` and then `(asdf:load-system \"py4cl2-cffi\" :force t)`\n\nThe later option allocates a dedicated thread to initialize and call python code. Unfortunately, this is both brittle (lisp process may hang) and incurs a performance penalty.\n\nFollowing either option should allow you to use matplotlib:\n\n```lisp\nPYTHON-LISP-USER\u003e (import-module \"matplotlib.pyplot\" :as \"plt\")\nT\nPYTHON-LISP-USER\u003e (pycall \"plt.plot\"\n                     (alexandria:iota 10)\n                     (mapcar (lambda (x) (* x x))\n                             (alexandria:iota 10)))\n#(#\u003cPYTHON-OBJECT :type \u003cclass 'matplotlib.lines.Line2D'\u003e\n  Line2D(_child0)\n {1006670F83}\u003e)\nPYTHON-LISP-USER\u003e (float-features:with-float-traps-masked t\n                    (pycall \"plt.show\"))\n#\u003cPYTHON-OBJECT :type \u003cclass 'NoneType'\u003e\n  None\n {1006672273}\u003e\n```\n\n\u003cimg margin=\"auto\" width=\"75%\" src=\"./plt-example.png\"\u003e\u003c/img\u003e\n\n## Optimization\n\nOut of the box, py4cl2-cffi is about 10-15 times slower than native CPython.\n\n```lisp\nPYTHON-LISP-USER\u003e (import-module \"math\")\nNIL\nPYTHON-LISP-USER\u003e (time\n                   (loop for i below 100000\n                         do (pycall \"math.sin\" i)))\nEvaluation took:\n  1.112 seconds of real time\n  1.109842 seconds of total run time (1.105747 user, 0.004095 system)\n  [ Real times consist of 0.052 seconds GC time, and 1.060 seconds non-GC time. ]\n  [ Run times consist of 0.052 seconds GC time, and 1.058 seconds non-GC time. ]\n  99.82% CPU\n  2,448,781,258 processor cycles\n  41,682,272 bytes consed\n\nNIL\n```\n\nA simple way to speed up is to surround the block of code in `(with-python-gil ...)` or `(pygil-ensure ...)` and `(pygil-release)`. However, lisp implementations usually perform garbage collection from separate thread. So, if `pyobject-wrapper`-objects are generated, there finalizers will never run until the GIL is released from the main thread.\n\n```lisp\nPYTHON-LISP-USER\u003e (time\n                   (with-python-gil\n                     (loop for i below 100000\n                           do (pycall \"math.sin\" i))))\nEvaluation took:\n  0.792 seconds of real time\n  0.794365 seconds of total run time (0.793760 user, 0.000605 system)\n  100.25% CPU\n  1,754,116,832 processor cycles\n  30,403,504 bytes consed\n\nNIL\n```\n\nA slightly more involved method involves setting `py4cl2-cffi/config:*disable-pystop*` to non-`NIL` before `py4cl2-cffi` is loaded. You may optionally need to compile `py4cl2-cffi` again. Thus -\n\n```lisp\n;;; On a fresh lisp image\n(asdf:load-system \"py4cl2-cffi/config\" :silent t)\n(setf py4cl2-cffi/config:*disable-pystop* t)\n(asdf:load-system \"py4cl2-cffi\" :force t)\n\n(defpackage :python-lisp-user\n  (:use :cl :py4cl2-cffi))\n\n(in-package :python-lisp-user)\n```\n\nThis will allow the compiler macro of `pycall` to dump the pointer to the callable into the compiled code. For example, see the `SB-SYS:INT-SAP` below.\n\n```lisp\nPYTHON-LISP-USER\u003e (disassemble\n                   (compile nil `(lambda (x)\n                                   (pycall \"math.sin\" x))))\n; disassembly for (LAMBDA (X))\n; Size: 35 bytes. Origin: #x557DD5CF                          ; (LAMBDA (X))\n; CF:       498B4510         MOV RAX, [R13+16]                ; thread.binding-stack-pointer\n; D3:       488945F8         MOV [RBP-8], RAX\n; D7:       488B15BAFFFFFF   MOV RDX, [RIP-70]                ; #.(SB-SYS:INT-SAP #X753F7547FD80)\n; DE:       488BFE           MOV RDI, RSI\n; E1:       B904000000       MOV ECX, 4\n; E6:       FF7508           PUSH QWORD PTR [RBP+8]\n; E9:       B8828FB550       MOV EAX, #x50B58F82              ; #\u003cFDEFN PY4CL2-CFFI::%PYCALL\u003e\n; EE:       FFE0             JMP RAX\n; F0:       CC10             INT3 16                          ; Invalid argument count trap\nNIL\n```\n\nThe impact on performance:\n\n```lisp\nPYTHON-LISP-USER\u003e (time\n                   (loop for i below 100000\n                         do (pycall \"math.sin\" i)))\nEvaluation took:\n  0.520 seconds of real time\n  0.521538 seconds of total run time (0.521249 user, 0.000289 system)\n  100.38% CPU\n  1,151,647,842 processor cycles\n  17,628,400 bytes consed\n\nNIL\nPYTHON-LISP-USER\u003e (time\n                   (with-python-gil\n                     (loop for i below 100000\n                           do (pycall \"math.sin\" i))))\nEvaluation took:\n  0.324 seconds of real time\n  0.322341 seconds of total run time (0.322341 user, 0.000000 system)\n  99.38% CPU\n  711,752,998 processor cycles\n  9,600,240 bytes consed\n\nNIL\n```\n\n[defpymodule](#defpymodule) and [defpyfun](#defpyfun) add an `(import-module ...)` form to the lisp function bodies when `:safety` is non-NIL (default). With this, the function can work correctly even if it was called after a `(pystop)`. With `*disable-pystop*` set to NIL (default), `(pystop)` clears the global namespace. The `(import-module ...)` before the actual `pycall` can then import the module back to the global namespace. However, this incurs a performance penalty.\n\n```lisp\nPYTHON-LISP-USER\u003e (defpyfun \"sin\" \"math\" :lisp-fun-name \"PYSIN\")\nPYSIN\nPYTHON-LISP-USER\u003e (time\n                   (loop for i below 10000\n                         do (pysin i)))\nEvaluation took:\n  2.872 seconds of real time\n  2.928811 seconds of total run time (2.508456 user, 0.420355 system)\n  101.98% CPU\n  6,349,788,686 processor cycles\n  26,563,104 bytes consed\n\nNIL\n```\n\nBy passing `:safety nil`, the `(import-module ...)` form can be avoided.\n\n```lisp\nPYTHON-LISP-USER\u003e (defpyfun \"sin\" \"math\" :lisp-fun-name \"PYSIN\"\n                    :safety nil)\nWARNING: redefining PYTHON-LISP-USER::PYSIN in DEFUN\nPYSIN\nPYTHON-LISP-USER\u003e (time\n                   (loop for i below 10000\n                         do (pysin i)))\nEvaluation took:\n  0.060 seconds of real time\n  0.060613 seconds of total run time (0.060607 user, 0.000006 system)\n  101.67% CPU\n  133,806,218 processor cycles\n  2,424,544 bytes consed\n\nNIL\n```\n\nOverall, these measures allow py4cl2-cffi to be within a factor of 5 of CPython. See [./perf-compare/](./perf-compare/).\n\n# Developer Thoughts on Garbage Collection\n\nIf you are working with raw pointers, then all bets are off about handling garbage collection.\n\nThus, the only time garbage collection should \"work correctly\" aka - without (i) segmentation faults (ii) memory leaks - is when you are *not* working with raw pointers. In other words, functions that return lisp values *must* perform garbage collection.\n\nAn exhaustive list of functions that return lisp values include:\n\n- pyvalue\n- pyslot-value\n- pymethod\n- pycall\n- pyhelp\n- pyslot-list\n- pymethod-list\n\nEven amongst these, GC should not take place until the top level call has done its processing. To be more intrincate seems to require a merge of the python interpreter with the lisp interpreter.\n\nThe single place which decides what to *not* collect is the function \"lispify\" when it returns a python-object struct-wrapper around the pyobject. In these cases, we PYUNTRACK the pointers.\n\nDuring DecRef-ing through an object finalizer, one needs to hold the GIL, because at least on SBCL, the finalizer may be called through any thread. DecRef-ing without holding the GIL results in segmentation faults.\n\n\n# API Reference\n\n### \\*additional-init-codes\\*\n\n```lisp\nVariable\nDefault Value: NIL\n```\n\nA list of strings each of which should be python code. All the code\nwill be executed by [pystart](#pystart).\n\n### \\*defpymodule-silent-p\\*\n\n```lisp\nVariable\nDefault Value: NIL\n```\n\n[defpymodule](#defpymodule) avoids printing progress if this is T.\n\n### \\*internal-features\\*\n\n```lisp\nVariable\nDefault Value: (:TYPED-ARRAYS :WITH-PYTHON-OUTPUT)\n```\n\nA list of PY4CL2 features available on the system. (Support for :ARRAYS\nrequires numpy and is only determinable after python process has started.)\n\nThe list can include one or more of:\n\n  [:with-python-output](#with-python-output)\n  :TYPED-ARRAYS\n\n\n### \\*lispifiers\\*\n\n```lisp\nVariable\nDefault Value: NIL\n```\n\nEach entry in the alist *LISPIFIERS* maps from a lisp-type to\na single-argument lisp function. This function takes as input the \"default\" lisp\nobjects and is expected to appropriately parse it to the corresponding lisp object.\n\nNOTE: This is a new feature and hence unstable; recommended to avoid in production code.\n\n### \\*print-pyobject\\*\n\n```lisp\nVariable\nDefault Value: T\n```\n\nIf non-NIL, python's 'str' is called on the python-object before printing.\n\n### \\*print-pyobject-wrapper-identity\\*\n\n```lisp\nVariable\nDefault Value: T\n```\n\nIf non-NIL, print's the lisp type and identity of the pyobject-wrapper.\n\n### \\*pygc-threshold\\*\n\n```lisp\nVariable\nDefault Value: 1000\n```\n\nNumber of references in *PYTHON-NEW-REFERENCES* after which PYGC manipulates reference counts.\n\n### \\*pythonizers\\*\n\n```lisp\nVariable\nDefault Value: NIL\n```\n\nEach entry in the alist *PYTHONIZERS* maps from a lisp-type to\na single-argument PYTHON-FUNCTION-DESIGNATOR. This python function takes as input the\n\"default\" python objects and is expected to appropriately convert it to the corresponding\npython object.\n\nNOTE: This is a new feature and hence unstable; recommended to avoid in production code.\n\n### +disable-pystop+\n\n```lisp\nConstant: NIL\n```\n\n### +py-empty-tuple+\n\nNo documentation found for `+py-empty-tuple+`\n\n### +py-empty-tuple-pointer+\n\nNo documentation found for `+py-empty-tuple-pointer+`\n\n### +py-none+\n\nNo documentation found for `+py-none+`\n\n### +py-none-pointer+\n\nNo documentation found for `+py-none-pointer+`\n\n### +python-call-mode+\n\n```lisp\nConstant: DEDICATED-THREAD\n```\n\nPY4CL2-CFFI:+PYTHON-CALL-MODE+ is a constant assigned to the value of\nPY4CL2-CFFI/CONFIG:*PYTHON-CALL-MODE* at compile time. Thus, if\nPY4CL2-CFFI/CONFIG:*PYTHON-CALL-MODE* is changed, PY4CL2-CFFi must be recompiled.\n\nPossible values are :STANDARD and :DEDICATED-THREAD\n\n- When the value is :DEDICATED-THREAD, a dedicated thread is used for starting\n  the embedded python interpreter. All calls to python from lisp are made\n  through this thread. This may be required for using libraries like matplotlib\n  that expect all calls from the single thread that loads the library. On the\n  other hand, in emacs, each lisp buffer may communicate to the lisp process\n  (and thus python) through separate threads. It is however possible to\n  configure emacs and slime/swank to communicate using a single thread. See\n  SWANK:*COMMUNICATION-STYLE*. Python call mode provides a way of sidestepping\n  the slime/swank configuration.\n\n- When the value is :STANDARD, no such thread is created. Calls to python can\n  take place from any thread.\n\n### chain\n\n```lisp\nMacro: (chain \u0026rest chain)\n```\n\nThis is inspired by PARENSCRIPT:CHAIN, discussed in this issue:\n    https://github.com/bendudson/py4cl/issues/4\n\nIn python it is quite common to apply a chain of method calls, data member\naccess, and indexing operations to an object. To make this work smoothly in\nLisp, there is the chain macro (Thanks to @kat-co and parenscript for the\ninspiration). This consists of a target object, followed by a chain of\noperations to apply. For example\n\n    (chain \"hello {0}\" (format \"world\") (capitalize)) ; =\u003e \"Hello world\"\n\nwhich is interpreted as: \"hello {0}\".format(\"world\").capitalize().\n\nOr -\n\n    (chain \"hello {0}\" (format \"world\") (capitalize) (aref 1)) ; =\u003e \"e\"\n\nwhich is interpreted as: \"hello {0}\".format(\"world\").capitalize()[1].\n\n`chain` has two variants: `chain` is a macro, with the elements of `chain`\nunevaluated, while [chain\\*](#chain) is a function with its elements (arguments) evaluated\naccording to a normal lisp function call..\n\nSome more examples are as follows:\n\n    (chain (slice 3) stop) ; =\u003e 3\n    (let ((format-str \"hello {0}\")\n          (argument \"world\"))\n     (chain* format-str `(format ,argument))) ; =\u003e \"hello world\"\n\nArguments to methods are lisp, since only the top level forms in chain are\ntreated specially:\n\n    CL-USER\u003e (chain (slice 3) stop)\n    3\n    CL-USER\u003e (let ((format-str \"hello {0}\")\n                   (argument \"world\"))\n               (chain* format-str `(format ,argument)))\n    \"hello world\"\n    CL-USER\u003e (chain* \"result: {0}\" `(format ,(+ 1 2)))\n    \"result: 3\"\n    CL-USER\u003e (chain (aref \"hello\" 4))\n    \"o\"\n    CL-USER\u003e (chain (aref \"hello\" (slice 2 4)))\n    \"ll\"\n    CL-USER\u003e (chain (aref #2A((1 2 3) (4 5 6)) (slice 0 2)))\n    #2A((1 2 3) (4 5 6))\n    CL-USER\u003e (chain (aref #2A((1 2 3) (4 5 6)) 1 (slice 0 2))) ; array[1, 0:2]\n    #(4 5)\n    CL-USER\u003e (pyexec \"class TestClass:\n        def doThing(self, value = 42):\n            return value\")\n    CL-USER\u003e (chain (\"TestClass\") (\"doThing\" :value 31))\n    31\n\nThere is also `(SETF `chain`)`. However, this is experimental. This requires that\nthe python object remains as a wrapper. [with-remote-objects](#with-remote-objects) can ensure\nthis. [with-remote-objects\\*](#with-remote-objects) additionally lispifies the return value:\n\n    CL-USER\u003e (with-remote-objects*\n               (let ((lst (pycall \"list\" '(1 2 3))))\n                 (setf (chain* `(aref ,lst 0)) 4)\n                 lst))\n    #(4 2 3)\n\nNote that this modifies the value in python, so the above example only works\nbecause `lst` is a pyobject-wrapper, rather than a lisp object.\nThe following therefore does not work:\n\n    CL-USER\u003e (let ((lst (pycall \"list\" '(1 2 3))))\n               (setf (chain* `(aref ,lst 0)) 4)\n               lst)\n    #(1 2 3)\n\n\n### chain\\*\n\n```lisp\nFunction: (chain* \u0026rest chain)\n```\n\n### define-lispifier\n\n```lisp\nMacro: (define-lispifier name (pyobject-var) \u0026body body)\n```\n\n### defpyfun\n\n```lisp\nMacro: (defpyfun fun-name \u0026optional pymodule-name \u0026key (as fun-name) (cache t)\n        (lisp-fun-name (lispify-name as)) (lisp-package *package*) (safety t))\n```\n\n\nDefines a function which calls python\n\nExample\n\n    (py4cl:pyexec \"import math\")\n    (py4cl:defpyfun \"math.sqrt\")\n    (math.sqrt 42) -\u003e 6.4807405\n\nArguments:\n\n- FUN-NAME: name of the function in python, before import\n- PYMODULE-NAME: name of the module containing `fun-name`\n\n- AS: name of the function in python, after import\n- CACHE: if non-NIL, constructs the function body at macroexpansion time\n- LISP-FUN-NAME: name of the lisp symbol to which the function is bound*\n- LISP-PACKAGE: package (not its name) in which `lisp-fun-name` will be interned\n- SAFETY: if T, adds an additional line in the function asking to import the\n    package or function, so that the function works even after [pystop](#pystop) is called.\n    However, this increases the overhead of stream communication, and therefore,\n    can reduce speed.\n\n\n### defpymodule\n\n```lisp\nMacro: (defpymodule pymodule-name \u0026optional (submodules NIL) \u0026key (cache t)\n        (continue-ignoring-errors t)\n        (lisp-package (lispify-name pymodule-name)) (reload t)\n        (recompile-on-change NIL) (safety t) (silent *defpymodule-silent-p*))\n```\n\n\nImport a python module (and its submodules) as a lisp-package(s).\n\nExample:\n\n    (py4cl:defpymodule \"math\" :lisp-package \"M\")\n    (m:sqrt 4)   ; =\u003e 2.0\n\nArguments:\n\n- PYMODULE-NAME: name of the module in python, before importing\n- SUBMODULES:\n    - can be NIL if no lisp packages corresponding to submodules are to be defined\n    - can be T to define lisp packages corresponding to non-hidden submodules\n    - can be :HIDDEN to define lisp packages corresponding to both\n      hidden as well as non-hidden submodules\n    - can be a LIST or nested alist of submodule names. Each element of the list\n      is either a string, or a nested alist mapping the submodule\n      name to its subsubmodule names in the same format as `submodules`\n\n- CONTINUE-IGNORING-ERRORS: This is set to non-NIL for convenience.\n    Set to NIL while debugging. When this is NIL, all kinds of errors\n    will be signalled instead of being suppressed silently.\n\n-  CACHE: if non-NIL, produces the DEFPACKAGE and DEFUN forms at macroexpansion time to speed-up future reloads of the system\n- LISP-PACKAGE: lisp package, in which to intern (and export) the callables\n- RECOMPILE-ON-CHANGE: the name of the ASDF system to recompile if the python version of\n    `pymodule-name` changes; this only has effect if `cache` is non-NIL\n- RELOAD: redefine the `lisp-package` if T\n- SAFETY: value of safety to pass to defpyfun; see defpyfun\n- SILENT: prints \"status\" lines when NIL\n\n\n### disable-pygc\n\n```lisp\nMacro: (disable-pygc)\n```\n\n### enable-pygc\n\n```lisp\nMacro: (enable-pygc)\n```\n\n### export-function\n\n```lisp\nFunction: (export-function function python-name)\n```\n\nMakes a lisp `function` available in python process as `python-name`\n\n### import-function\n\n```lisp\nFunction: (import-function name from \u0026key (as NIL))\n```\n\n### import-module\n\n```lisp\nFunction: (import-module name \u0026key (as NIL))\n```\n\n### pycall\n\n```lisp\nFunction: (pycall python-callable \u0026rest args)\n```\n\nIf `python-callable` is a string or symbol, it is treated as the name of a\npython callable, which is then retrieved using PYVALUE*\n\n### pyerror\n\n```lisp\nCondition\n```\n\nA lisp error to indicate all kinds of python error.\n\n\n### pyeval\n\n```lisp\nFunction: (pyeval \u0026rest args)\n```\n\n### pyexec\n\n```lisp\nFunction: (pyexec \u0026rest args)\n```\n\n### pyfor\n\n`(iterate (PYFOR var IN pyobject-wrapper) ...)`\n\n### pygenerator\n\n```lisp\nFunction: (pygenerator function stop-value)\n```\n\n### pyhelp\n\n```lisp\nFunction: (pyhelp string-or-python-callable)\n```\n\n### pymethod\n\n```lisp\nFunction: (pymethod object method-name \u0026rest args)\n```\n\n### pymethod-list\n\n```lisp\nFunction: (pymethod-list pyobject \u0026key (as-vector NIL))\n```\n\n### pyobject-wrapper\n\n```lisp\nStructure\n```\n\nA wrapper around a pointer to a python object.\nLOAD-FORM is used if the pyobject-wrapper is dumped into a compiled lisp file.\n\n\n### pyobject-wrapper-eq\n\n```lisp\nFunction: (pyobject-wrapper-eq o1 o2)\n```\n\nReturns T if `o1` and `o2` are both [pyobject-wrapper](#pyobject-wrapper) with the same pointer, or\nthe same lisp objects which are EQ to each other. Returns NIL in all other cases.\n\n### pyobject-wrapper-eq\\*\n\n```lisp\nFunction: (pyobject-wrapper-eq* o1 o2)\n```\n\nLike [pyobject-wrapper-eq](#pyobject-wrapper-eq) but assumes that `o1` and `o2` are [pyobject-wrapper](#pyobject-wrapper) each.\n\n### pyref\n\n```lisp\nFunction: (pyref object \u0026rest indices)\n```\n\nWrapper around python's __getitem__ along with support for slicing.\n\nExample\n\n    (pyref \"hello\" 1) ;=\u003e \"e\"\n    ; NOTE: Python does not make a distinction between strings and characters\n\n    (pyref #(1 2 3) 1) ;=\u003e 2\n    (pyref #(1 2 3 4 5) '(slice 3 5)) ;=\u003e #(4 5)\n\n    (import-module \"numpy\")\n    (with-remote-objects*\n      (pyref (chain (\"numpy.arange\" 12) (\"reshape\" (3 4)))\n             `(slice ,+py-none+)\n             0))\n\n\n\n### pyslot-list\n\n```lisp\nFunction: (pyslot-list pyobject \u0026key (as-vector NIL))\n```\n\n### pyslot-value\n\n```lisp\nFunction: (pyslot-value object slot-name)\n```\n\n### pystart\n\n```lisp\nFunction: (pystart \u0026key (verbose *pystart-verbosity*))\n```\n\n### pystop\n\n```lisp\nFunction: (pystop)\n```\n\nWhen +PYTHON-CALL-MODE+ is :DEDICATED-THREAD or when +DISABLE-PYSTOP+ is T\nthis is a NO-OP.\n\n### python-alive-p\n\n```lisp\nFunction: (python-alive-p)\n```\n\n### python-getattr\n\n```lisp\nGeneric Function: (python-getattr object slot-name)\n```\n\nCalled when python accesses an object's slot (__getattr__)\n\n### python-setattr\n\n```lisp\nGeneric Function: (python-setattr object slot-name value)\n```\n\nCalled when python sets an object's slot (__setattr__)\n\n### python-start-if-not-alive\n\n```lisp\nFunction: (python-start-if-not-alive)\n```\n\n### pythonize\n\n```lisp\nGeneric Function: (pythonize lisp-value-or-object)\n```\n\nGiven a lisp object, return a CFFI:FOREIGN-POINTER pointing to the python object corresponding to the given lisp object.\n\nThe implemented methods are expected to return a new (strong) reference\nto the python object. The method is also expected to call PYTRACK\nto notify the PYGC functionality to delete the reference once the object\nis no longer needed.\n\nSee the documentation for PYGC to understand when reference deletion\ntakes place.\n\n### pyvalue\n\n```lisp\nFunction: (pyvalue python-name-or-variable)\n```\n\nGet the value of a python-name-or-variable.\n\nExample:\n\n    (pyvalue \"sys\") ;=\u003e \u003cmodule 'sys' (built-in)\u003e\n    (pyvalue \"sys.path\")\n    ;=\u003e\n      #(\"/home/user/miniconda3/lib/python310.zip\"\n        \"/home/user/miniconda3/lib/python3.10\"\n        \"/home/user/miniconda3/lib/python3.10/lib-dynload\"\n        \"/home/user/miniconda3/lib/python3.10/site-packages\")\n\n\n### pyversion-info\n\n```lisp\nFunction: (pyversion-info)\n```\n\nReturn a list, using the result of python's sys.version_info.\n\n### raw-pyeval\n\n```lisp\nFunction: (raw-pyeval \u0026rest code-strings)\n```\n\n\nUnlike PY4CL or PY4CL2, the use of RAW-PY, `raw-pyeval` and [raw-pyexec](#raw-pyexec),\n[pyeval](#pyeval), [pyexec](#pyexec) should be avoided unless necessary.\nInstead, use [pycall](#pycall), [pyvalue](#pyvalue), (SETF [pyvalue](#pyvalue)), [pyslot-value](#pyslot-value), (SETF [pyslot-value](#pyslot-value)), and [pymethod](#pymethod).\n\nRAW-PY, `raw-pyeval`, [raw-pyexec](#raw-pyexec) are only provided for backward compatibility.\n\n### raw-pyexec\n\n```lisp\nFunction: (raw-pyexec \u0026rest code-strings)\n```\n\n\nUnlike PY4CL or PY4CL2, the use of RAW-PY, [raw-pyeval](#raw-pyeval) and `raw-pyexec`,\n[pyeval](#pyeval), [pyexec](#pyexec) should be avoided unless necessary.\nInstead, use [pycall](#pycall), [pyvalue](#pyvalue), (SETF [pyvalue](#pyvalue)), [pyslot-value](#pyslot-value), (SETF [pyslot-value](#pyslot-value)), and [pymethod](#pymethod).\n\nRAW-PY, [raw-pyeval](#raw-pyeval), `raw-pyexec` are only provided for backward compatibility.\n\n### simple-pyerror\n\n```lisp\nCondition\n```\n\nA specialization of PYERROR to hold the python error type.\n\n\u003cu\u003e**Direct Slots**\u003c/u\u003e\n\n**type**\n```lisp\nInitargs: :TYPE\n```\n\n### with-lispifiers\n\n```lisp\nMacro: (with-lispifiers (\u0026rest overriding-lispifiers) \u0026body body)\n```\n\nEach entry of `overriding-lispifiers` is a two-element list of the form\n\n    (TYPE LISPIFIER)\n\nHere, TYPE is unevaluated, while LISPIFIER will be evaluated; the LISPIFIER is expected\nto take a default-lispified object (see lisp-python types translation table in docs)\nand return the appropriate object user expects.\n\nFor example,\n\n    (raw-pyeval \"[1, 2, 3]\") ;=\u003e #(1 2 3) ; the default lispified object\n    (with-lispifiers ((vector (lambda (x) (coerce x 'list))))\n      (print (raw-pyeval \"[1,2,3]\"))\n      (print (raw-pyeval \"5\")))\n    ; #(1 2 3) ; default lispified object\n    ; (1 2 3)  ; coerced to LIST by the lispifier\n    ; 5        ; lispifier uncalled for non-VECTOR\n    5\n\nNOTE: This is a new feature and hence unstable; recommended to avoid in production code.\n\n### with-pygc\n\n```lisp\nMacro: (with-pygc \u0026body body)\n```\n\nCode surrounded by `with-pygc` performs garbage collection\nonly after executing all of `body`.\n\n### with-python-error-output\n\n```lisp\nMacro: (with-python-error-output \u0026body forms-decl)\n```\n\nGets the output of the python program executed in `forms-decl` in the form a string.\n\n### with-python-gil\n\n```lisp\nMacro: (with-python-gil \u0026body body)\n```\n\n### with-python-output\n\n```lisp\nMacro: (with-python-output \u0026body forms-decl)\n```\n\nGets the output of the python program executed in `forms-decl` in the form a string.\n\n### with-pythonizers\n\n```lisp\nMacro: (with-pythonizers (\u0026rest overriding-pythonizers) \u0026body body)\n```\n\nEach entry of `overriding-pythonizers` is a two-element list of the form\n\n    (TYPE PYTHONIZER)\n\nHere, TYPE is unevaluated, while PYTHONIZER will be evaluated; the PYTHONIZER is expected\nto take a default-pythonized object (see lisp-python types translation table in docs)\nand return the appropriate object user expects.\n\nFor example,\n\n    ; A convenience function\n    (defun pyprint (object)\n      (pycall \"print\" object)\n      (pycall \"sys.stdout.flush\")\n      (values))\n\n    (pyprint #(1 2 3)) ; prints [1, 2, 3] ; the default object\n    (with-pythonizers ((vector \"tuple\"))\n      (pyprint #(1 2 3))\n      (pyprint 5))\n    ; (1, 2, 3) ; coerced to tuple by the pythonizer\n    ; 5         ; pythonizer uncalled for non-VECTOR\n    5\n\nNOTE: This is a new feature and hence unstable; recommended to avoid in production code.\n\n### with-remote-objects\n\n```lisp\nMacro: (with-remote-objects \u0026body body)\n```\n\nEnsures that all values returned by python functions\nand methods are kept in python, and only pointers are returned to lisp.\nThis is useful if performing operations on large datasets.\n\n### with-remote-objects\\*\n\n```lisp\nMacro: (with-remote-objects* \u0026body body)\n```\n\nEnsures that all values returned by python functions\nand methods are kept in python, and only handles returned to lisp.\nThis is useful if performing operations on large datasets. Unlike\n[with-remote-objects](#with-remote-objects), evaluates the last result and returns not just a handle.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdigikar99%2Fpy4cl2-cffi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdigikar99%2Fpy4cl2-cffi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdigikar99%2Fpy4cl2-cffi/lists"}