forked from clj-python/libpython-clj
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpython.clj
More file actions
241 lines (203 loc) · 7.35 KB
/
python.clj
File metadata and controls
241 lines (203 loc) · 7.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
(ns libpython-clj.python
(:require [tech.parallel.utils :refer [export-symbols]]
[libpython-clj.python.interop :as pyinterop]
[libpython-clj.python.interpreter :as pyinterp
:refer [with-gil with-interpreter]]
[libpython-clj.python.object :as pyobj]
[libpython-clj.python.bridge]
[libpython-clj.jna :as pyjna]
[tech.jna :as jna])
(:import [com.sun.jna Pointer]
[com.sun.jna.ptr PointerByReference]
[java.io Writer]
[libpython_clj.jna PyObject]))
(set! *warn-on-reflection* true)
(export-symbols libpython-clj.python.protocols
python-type
dir
att-type-map
get-attr
has-attr?
set-attr!
callable?
has-item?
get-item
set-item!
call
call-kw
call-attr
call-attr-kw
len
as-map
as-list
as-tensor)
(export-symbols libpython-clj.python.object
->py-dict
->py-float
->py-list
->py-long
->py-string
->py-tuple
->py-fn
->python
->jvm)
(export-symbols libpython-clj.python.interop
libpython-clj-module-name)
(export-symbols libpython-clj.python.bridge
as-jvm
as-python
->numpy
as-numpy)
(defn run-simple-string
"Run a string expression returning a map of
{:globals :locals :result}.
This uses the global __main__ dict under the covers so it matches the behavior
of the cpython implementation with the exception of returning the various maps
used.
Note this will never return the result of the expression:
https://mail.python.org/pipermail/python-list/1999-April/018011.html
Globals, locals may be provided but are not necessary.
Implemented in cpython as:
PyObject *m, *d, *v;
m = PyImport_AddModule(\"__main__\");
if (m == NULL)
return -1;
d = PyModule_GetDict(m);
v = PyRun_StringFlags(command, Py_file_input, d, d, flags);
if (v == NULL) {
PyErr_Print();
return -1;
}
Py_DECREF(v);
return 0;"
[program & {:keys [globals locals]}]
(->> (pyinterop/run-simple-string program :globals globals :locals locals)
(map (fn [[k v]]
[k (as-jvm v)]))
(into {})))
(defn run-string
"Wrapper around the python c runtime PyRun_String method. This requires you to
understand what needs to be in the globals and locals dict in order for everything
to work out right and for this reason we recommend run-simple-string."
[program & {:keys [globals locals]}]
(->> (pyinterop/run-string program :globals globals :locals locals)
(map (fn [[k v]]
[k (as-jvm v)]))
(into {})))
(defn import-module
"Import a python module. Returns a bridge"
[modname]
(-> (pyinterop/import-module modname)
(as-jvm)))
(defn add-module
"Add a python module. Returns a bridge"
[modname]
(-> (pyinterop/add-module modname)
(as-jvm)))
(defn module-dict
"Get the module dictionary. Returns bridge."
[module]
(-> (pyinterop/module-dict module)
as-jvm))
(defn initialize!
"Initialize the python library. If library path is provided, then the python
:library-path Library path of the python library to use.
:program-name - optional but will show up in error messages from python.
:no-io-redirect - there if you don't want python stdout and stderr redirection
to *out* and *err*."
[& {:keys [program-name no-io-redirect? library-path]}]
(when library-path
(alter-var-root #'libpython-clj.jna.base/*python-library*
(constantly library-path)))
(when-not @pyinterp/*main-interpreter*
(pyinterp/initialize! program-name)
;;setup bridge mechansim and io redirection
(pyinterop/register-bridge-type!)
(when-not no-io-redirect?
(pyinterop/setup-std-writer #'*err* "stderr")
(pyinterop/setup-std-writer #'*out* "stdout")))
:ok)
(defn jvm-type-test
[]
(initialize! :no-io-redirect? true)
(let [retval
(-> (add-module "libpython_clj")
(get-attr "jvm_bridge_type")
(jna/as-ptr))]
(println (.ob_refcnt (libpython_clj.jna.PyObject. retval)))
retval))
(defn ptr-refcnt
[item]
(-> (jna/as-ptr item)
(libpython_clj.jna.PyObject. )
(.ob_refcnt)))
(defn finalize!
"Finalize the interpreter. You probably shouldn't call this as it destroys the
global interpreter and reinitialization is unsupported cpython."
[]
(pyinterp/finalize!))
(defn python-pyerr-fetch-error-handler
"Utility code used in with macro"
[]
(let [ptype# (PointerByReference.)
pvalue# (PointerByReference.)
ptraceback# (PointerByReference.)
_# (pyjna/PyErr_Fetch ptype# pvalue# ptraceback#)
ptype# (-> (jna/->ptr-backing-store ptype#)
(pyobj/wrap-pyobject true))
pvalue# (-> (jna/->ptr-backing-store pvalue#)
(pyobj/wrap-pyobject true))
ptraceback# (-> (jna/->ptr-backing-store ptraceback#)
(pyobj/wrap-pyobject true))]
;;We own the references so they have to be released.
(throw (ex-info "python error in flight"
{:ptype ptype#
:pvalue pvalue#
:ptraceback ptraceback#}))))
(defn with-exit-error-handler
"Utility code used in with macro"
[with-var error]
(let [einfo (ex-data error)]
(if (every? #(contains? einfo %) [:ptype :pvalue :ptraceback])
(let [{ptype :ptype
pvalue :pvalue
ptraceback :ptraceback} einfo
suppress-error? (call-attr with-var "__exit__"
ptype
pvalue
ptraceback)]
(when (and ptype pvalue ptraceback
(not suppress-error?))
(do
;;MAnuall incref here because we cannot detach the object
;;from our gc decref hook added during earlier pyerr-fetch handler.
(pyjna/Py_IncRef ptype)
(pyjna/Py_IncRef pvalue)
(pyjna/Py_IncRef ptraceback)
(pyjna/PyErr_Restore ptype pvalue ptraceback)
(pyinterp/check-error-throw))))
(do
(call-attr with-var "__exit__" nil nil nil)
(throw error)))))
(defmacro with
"Support for the 'with' statement in python:
(py/with [item (py/call-attr testcode-module \"WithObjClass\" true fn-list)]
(py/call-attr item \"doit_err\"))"
[bind-vec & body]
(when-not (= 2 (count bind-vec))
(throw (Exception. "Bind vector must have 2 items")))
(let [varname (first bind-vec)]
`(with-gil
(let [~@bind-vec]
(with-bindings
{#'libpython-clj.python.interpreter/*python-error-handler*
python-pyerr-fetch-error-handler}
(call-attr ~varname "__enter__")
(try
(let [retval#
(do
~@body)]
(call-attr ~varname "__exit__" nil nil nil)
retval#)
(catch Throwable e#
(with-exit-error-handler ~varname e#))))))))