Skip to content

Commit 06da141

Browse files
authored
Add support for Lisp functions to be called with Python keyword arguments (#528)
* Support calling Python functions with keyword arguments * Apply decorators based on the metadata key * Fix invalid local reference * Simplify it all * Oop * Add kwarg support to deftype members too * Fix bad merge * Some cleanup * Python Interop docs * Fix the docs up a bit * Tests for fn * Spell good * Test deftype methods * Test the property check * Doc fix * Lint
1 parent c4393d4 commit 06da141

File tree

9 files changed

+516
-15
lines changed

9 files changed

+516
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
### Added
99
* Added support for `future`s (#441)
1010
* Added support for calling Python functions and methods with keyword arguments (#531)
11+
* Added support for Lisp functions being called with keyword arguments (#528)
1112

1213
### Fixed
1314
* Fixed a bug where the Basilisp AST nodes for return values of `deftype` members could be marked as _statements_ rather than _expressions_, resulting in an incorrect `nil` return (#523)

docs/pyinterop.rst

Lines changed: 194 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,197 @@
33
Python Interop
44
==============
55

6-
TBD
6+
Basilisp features myriad options for interfacing with host Python code.
7+
8+
.. contents::
9+
:depth: 2
10+
11+
.. _name_munging:
12+
13+
Name Munging
14+
------------
15+
16+
Per Python's `PEP 8 naming conventions <https://www.python.org/dev/peps/pep-0008/#naming-conventions>`_, Python method and function names frequently use ``snake_case``.
17+
Basilisp is certainly capable of reading ``snake_case`` names without any special affordance.
18+
However, Basilisp code (like many Lisps) tends to prefer ``kebab-case`` for word separation.
19+
20+
Since the hyphen character used in ``kebab-case`` cannot be used in valid identifiers in Python, the Basilisp compiler automatically converts names to Python-safe identifiers before compiling.
21+
In many of the examples below, you will see Python code referenced directly using ``kebab-case``.
22+
When compiled, a ``kebab-case`` identifier always becomes a ``snake_case`` identifier, so calling Python code from within Basilisp blends in fairly well with standard Basilisp code.
23+
24+
.. note::
25+
26+
The Basilisp compiler munges *all* unsafe Basilisp identifiers to safe Python identifiers, but other cases are unlikely to appear in standard Python interop usage.
27+
28+
.. _python_builtins:
29+
30+
Python Builtins
31+
---------------
32+
33+
Python features a collection of `builtin <https://docs.python.org/3.8/library/functions.html>`_ functions which are available by default without module qualification in all Python scripts.
34+
Python builtins are available in all Basilisp code as qualified symbols with the ``python`` namespace portion.
35+
It is not required to import anything to enable this functionality.
36+
37+
::
38+
39+
basilisp.user=> (python/abs -1)
40+
1
41+
42+
.. _importing_modules:
43+
44+
Importing Modules
45+
-----------------
46+
47+
As in standard Python, it is possible to import any module importable in native Python code in Basilisp using the ``(import module)`` macro.
48+
Submodules may be imported using the standard Python ``.`` separator: ``(import module.sub)``.
49+
50+
Upon import, top-level (unqualified) Python modules may be referred to using the full module name as the namespace portion of the symbol and the desired module member.
51+
Submodules will be available under the full, dot-separated name.
52+
53+
To avoid name clashes from the above, you may alias imports (as in native Python code) using the same syntax as ``require``.
54+
Both top-level modules and submodules may be aliased: ``(import [module.sub :as sm])``.
55+
Note that none of the other convenience features or flags from ``require`` are available, so you will not be able to, say, refer unqualified module members into the current Namespace.
56+
57+
.. warning::
58+
59+
Unlike in Python, imported module names and aliases cannot be referred to directly in Basilisp code.
60+
Module and Namespace names are resolved separately from local names and will not resolve as unqualified names.
61+
62+
.. code-block::
63+
64+
(import [os.path :as path])
65+
(path/exists "test.txt") ;;=> false
66+
67+
.. _referencing_module_members:
68+
69+
Referencing Module Members
70+
--------------------------
71+
72+
Once a Python module is imported into the current Namespace, it is trivial to reference module members directly.
73+
References to Python module members appear identical to qualified Basilisp Namespace references.
74+
Class constructors or other callables in the module can be called directly as a standard Basilisp function call.
75+
Static members and class members can be referenced by adding the class name to the (potentially) qualified symbol namespace, separated by a single ``.``.
76+
77+
.. code-block:: clojure
78+
79+
(import datetime)
80+
(datetime.datetime/now) ;;=> #inst "2020-03-30T08:56:57.176809"
81+
82+
.. _accessing_object_methods_and_props:
83+
84+
Accessing Object Methods and Properties
85+
---------------------------------------
86+
87+
Often when interfacing with native Python code, you will end up handling raw Python objects.
88+
In such cases, you may need or want to call a method on that object or access a property.
89+
Basilisp has specialized syntax support for calling methods on objects and accessing its properties.
90+
91+
To access an object's method, the ``.`` special form can be used: ``(. object method & args)``.
92+
93+
.. code-block:: clojure
94+
95+
(import datetime)
96+
(def now (datetime.datetime/now))
97+
(. now strftime "%Y-%m-%d") ;;=> "2020-03-31"
98+
99+
As a convenience, Basilisp offers a more compact syntax for method names known at compile time: ``(.method object & args))``.
100+
101+
.. code-block:: clojure
102+
103+
(.strftime now "%Y-%m-%d") ;;=> "2020-03-31"
104+
105+
In Python, objects often expose properties which can be read directly from the instance.
106+
To read properties from the instance, you can use the ``(.- object property)`` syntax.
107+
108+
.. code-block:: clojure
109+
110+
(.- now year) ;;=> 2020
111+
112+
As with methods, Basilisp features a convenience syntax for accessing properties whose names are statically known at compile time: ``(.-property object)``.
113+
114+
.. code-block:: clojure
115+
116+
(.-year now) ;;=> 2020
117+
118+
.. note::
119+
120+
Property references do not accept arguments and it is a compile-time error to pass arguments to an object property reference.
121+
122+
Though Basilisp generally eschews mutability, we live in a mutable world.
123+
Many Python frameworks and libraries rely on mutable objects as part of their public API.
124+
Methods may potentially always mutate their associated instance, but properties are often declared read-only.
125+
For properties which are explicitly *not* read only, you can mutate their value using the ``set!`` :ref:`special form <special_forms>`.
126+
127+
.. code-block:: clojure
128+
129+
(set! (.-property o) :new-value) ;;=> :new-value
130+
131+
.. note::
132+
133+
In most cases, Basilisp's method and property access features should be sufficient.
134+
However, in case it is not, Python's :ref:`builtins <python_builtins>` such as `getattr` and `setattr` are still available and can supplement Basilisp's interoperability features.
135+
136+
.. _keyword_arguments:
137+
138+
Keyword Arguments
139+
-----------------
140+
141+
Python functions and class constructors commonly permit callers to supply optional parameters as keyword arguments.
142+
While Basilisp functions themselves do not *typically* expose keyword arguments, Basilisp natively supports keyword argument calls with a number of different options.
143+
For function calls to statically known functions with a static set of keyword arguments, you can call your desired function and separate positional arguments from keyword arguments using the ``**`` special symbol.
144+
The Basilisp compiler expects 0 or more key/value pairs (similarly to the contents of a map literal) after the ``**`` symbol in a function or method call.
145+
It gathers all key/value pairs after that identifier, converts any keywords to valid Python identifiers (using the :ref:`name_munging` described above), and calls the Python function with those keyword arguments.
146+
147+
.. code-block:: clojure
148+
149+
(python/open "test.txt" ** :mode "w") ;;=> <_io.TextIOWrapper name='test.txt' mode='w' encoding='UTF-8'>
150+
151+
.. note::
152+
153+
The symbol ``**`` does not resolve to anything in Basilisp.
154+
The Basilisp compiler discards it during the analysis phase of compilation.
155+
156+
.. note::
157+
158+
It is also valid to supply keys as strings, though this is less idiomatic.
159+
String keys will also be munged to ensure they are valid Python identifiers.
160+
161+
.. _basilisp_functions_with_kwargs:
162+
163+
Basilisp Functions with Keyword Arguments
164+
-----------------------------------------
165+
166+
In rare circumstances (such as supplying a callback function), it may be necessary for a Basilisp function to support being called with Python keyword arguments.
167+
Basilisp can generate functions which can receive these keyword arguments and translate them into idiomatic Basilisp.
168+
Single-arity functions and ``deftype`` methods can declare support for Python keyword arguments with the ``:kwargs`` metadata key.
169+
Multi-arity functions and ``deftype`` methods do not support Python keyword arguments.
170+
For functions which do support keyword arguments, two strategies are supported for generating these functions: ``:apply`` and ``:collect``.
171+
172+
.. note::
173+
174+
Basilisp functions support a variant of keyword arguments via destructuring support provided by ``fn`` and ``defn``.
175+
The ``:apply`` strategy relies on that style of keyword argument support to idiomatically integrate with Basilisp functions.
176+
177+
.. code-block:: clojure
178+
179+
^{:kwargs :apply}
180+
(fn [& {:as kwargs}]
181+
kwargs)
182+
183+
The ``:apply`` strategy is appropriate in situations where there are few or no positional arguments defined on your function.
184+
With this strategy, the compiler converts the Python dict of string keys and values into a sequential stream of de-munged keyword and value pairs which are applied to the function.
185+
As you can see in the example above, this strategy fits neatly with the existing support for destructuring key and value pairs from rest arguments in a function definition.
186+
187+
.. warning::
188+
189+
With the ``:apply`` strategy, the Basilisp compiler cannot verify that the number of positional arguments matches the number defined on the receiving function, so use this strategy with caution.
190+
191+
.. code-block:: clojure
192+
193+
^{:kwargs :collect}
194+
(fn [arg1 arg2 ... {:as kwargs}]
195+
kwargs)
196+
197+
The ``:collect`` strategy is a better accompaniment to functions with positional arguments.
198+
With this strategy, Python keyword arguments are converted into a Basilisp map with de-munged keyword arguments and passed as the final positional argument of the function.
199+
You can use map destructuring on this final positional argument, just as you would with the map in the ``:apply`` case above.

docs/specialforms.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.. _special_forms:
2+
13
Special Forms
24
=============
35

0 commit comments

Comments
 (0)