Open dtenny opened 3 months ago
Just some food for thought, I have found these macros to be useful in writing app logic on top of py4cl2-cffi after also trying out libpython-clj.
(defun key-sym-str->str (attr-name)
(cond
((symbolp attr-name) (string-downcase (symbol-name attr-name)))
((stringp attr-name) attr-name)
(t (error "Only keywords, symbols, or strings can be used to access attributes, received ~s"
attr-name))))
(defmacro py.- (obj attr)
"Class/object getter syntax. (py.- obj attr) is equivalent to
Python's obj.attr syntax. Modeled after `libpython-clj`'s namesake macro.
However as python attributes generally have lower case names, and common lisp symbols
generally have upper case values, we will lowercase symbol names. If you need untranslated
attribute name case, pass a string."
`(pyslot-value ,obj ,(key-sym-str->str attr)))
(defmacro py. (object method-name &rest args)
"Class/object method syntax. (py. obj method arg1 arg2 ... argN)
is equivalent to Python's obj.method(arg1, arg2, ..., argN) syntax.
Modeled after `libpython-clj`'s namesake macro.
However as python attributes generally have lower case names, and common lisp symbols
generally have upper case values, we will lowercase symbol names. If you need untranslated
attribute name case, pass a string."
`(pymethod ,object ,(key-sym-str->str method-name) ,@args))
This lets me write code accessing returned data with a lot less syntax, for example this logic that creates a lispy map of python attributes for spaCy tokens in a spaCy document:
(defun pylist (pyobj)
"Invoke the python `list` function on pyobj, which should be an iterable, returns a _vector_."
(pycall "list" pyobj))
(defun tokens (doc)
"Return information about tokens in doc (as returned by `(nlp <text>)`).
Returns a sequence of maps, one for each token."
(map 'vector
(lambda (token)
(serapeum:dict
:py-token token ;original reference on further python calls
:text (py.- token text)
:lemma (py.- token lemma_)
:pos (py.- token pos_)
:tag (py.- token tag_)
:dep (py.- token dep_)
:shape (py.- token shape_)
:alpha (py.- token is_alpha)
:is_stop (py.- token is_stop)
;; Only available if the models have word vectors, small models don't.
:has-vector (py.- token has_vector)
:vector_norm (py.- token vector_norm)
:is_oov (py.- token is_oov)
))
(pylist doc)))```
Passing strings to the various `py-method` and similar functions was getting tedious. Of course some common lispers might be offended at the additional interned symbols, ymmv :-)
Another nice thing for libpython-clj is turning py objects that are iterable into seqable things in clojure (lazy sequence access to iterator content). I haven't done that in my playing around, I just call the the python `list` operator instead as noted in my example above.
If I was more advanced I'd probably just write some macro that automatically does the map conversions I like without the need for my tedious lambda+dict code, but I'm only messing around and it hasn't been worth the time yet. I also haven't looked at your numpy code to take inspirations, no doubt I'm doing it wrong, I haven't used python (willingly) in about 15 years, so I'm rusty.
Just curious if any of you have had a look at the Clojure libpython-clj or used it. Might be a useful source of ideas for select problems.
Having a look myself. There's also libpython-clj-examples.
Of personal interest is just that this is a clojure library that does not use JNI to access the python shared libraries.