Skip to content

Commit

Permalink
Equality/hash/better tostring for types.
Browse files Browse the repository at this point in the history
  • Loading branch information
cnuernber committed Dec 12, 2019
1 parent 94dbef6 commit 015af9c
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 17 deletions.
46 changes: 44 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,54 @@
# Time for a ChangeLog!


## 1.23-SNAPSHOT


Equals, hashcode, nice default .toString of python types:

```clojure
user> (require '[libpython-clj.python :as py])
nil
user> (def test-tuple (py/->py-tuple [1 2]))
#'user/test-tuple
user> (require '[libpython-clj.require :refer [require-python]])
nil
user> (require-python '[builtins :as bt])
nil
user> (bt/type test-tuple)
builtins.tuple
user> test-tuple
(1, 2)
user> (def new-tuple (py/->py-tuple [3 4]))
#'user/new-tuple
user> (= test-tuple new-tuple)
false
user> (= test-tuple (py/->py-tuple [1 2]))
true
user> (.hashCode test-tuple)
2130570162
user> (.hashCode (py/->py-tuple [1 2]))
2130570162
user> (require-python '[numpy :as np])
nil
user> (def np-ary (np/array [1 2 3]))
#'user/np-ary
user> np-ary
[1 2 3]
user> (bt/type np-ary)
numpy.ndarray
user> (py/python-type *1)
:type
```


## 1.22

Working to make more python environments work out of the box. Currently have a
testcase for conda working in a clean install of a docker container. There is now a
new method: `libpython-clj.python.interpreter/detect-startup-info` that attempts
call python3-config and python3 --version in order to automagically configure the
python library.
call `python3-config --prefix` and `python3 --version` in order to automagically
configure the python library.


## 1.21
Expand Down
1 change: 1 addition & 0 deletions src/libpython_clj/jna.clj
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
PyObject_Call
PyObject_CallObject
PyObject_Hash
PyObject_IsInstance
PyObject_IsTrue
PyObject_Not
PyObject_Length
Expand Down
28 changes: 26 additions & 2 deletions src/libpython_clj/jna/protocols/object.clj
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
keyword)
v]))
(into {})))
(def bool-fn-value-set (set (vals bool-fn-table)))


(defn bool-fn-constant
Expand All @@ -51,7 +52,7 @@
(long item)
(keyword? item)
(get bool-fn-table item))
value-set (set (vals bool-fn-table))]
value-set bool-fn-value-set]
(when-not (contains? value-set value)
(throw (ex-info (format "Unrecognized bool fn %s" item) {})))
(int value)))
Expand Down Expand Up @@ -309,10 +310,33 @@
Changed in version 3.2: The return type is now Py_hash_t. This is a signed integer
the same size as Py_ssize_t."
(size-t-type)
size-t-type
[o ensure-pyobj])


(def-pylib-fn PyObject_IsInstance
"Return 1 if inst is an instance of the class cls or a subclass of cls, or 0 if
not. On error, returns -1 and sets an exception.
If cls is a tuple, the check will be done against every entry in cls. The result
will be 1 when at least one of the checks returns 1, otherwise it will be 0.
If cls has a __instancecheck__() method, it will be called to determine the
subclass status as described in PEP 3119. Otherwise, inst is an instance of cls
if its class is a subclass of cls.
An instance inst can override what is considered its class by having a __class__
attribute.
An object cls can override if it is considered a class, and what its base
classes are, by having a __bases__ attribute (which must be a tuple of base
classes)."
Integer
[inst ensure-pyobj]
[cls ensure-pyobj])



(def-pylib-fn PyObject_IsTrue
"Returns 1 if the object o is considered to be true, and 0 otherwise. This is
equivalent to the Python expression not not o. On failure, return -1."
Expand Down
5 changes: 4 additions & 1 deletion src/libpython_clj/python.clj
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@
->jvm
make-tuple-fn
make-tuple-instance-fn
create-class)
create-class
is-instance?
hash-code
equals?)


(defmacro stack-resource-context
Expand Down
11 changes: 10 additions & 1 deletion src/libpython_clj/python/bridge.clj
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,16 @@
(as-jvm)))))
Object
(toString [this#]
(->jvm (py-proto/call-attr pyobj# "__str__")))
(with-interpreter interpreter#
(if (= 1 (libpy/PyObject_IsInstance pyobj# (libpy/PyType_Type)))
(format "%s.%s"
(->jvm (py-proto/get-attr pyobj# "__module__"))
(->jvm (py-proto/get-attr pyobj# "__name__")))
(->jvm (py-proto/call-attr pyobj# "__str__")))))
(equals [this# other#]
(pyobj/equals? this# other#))
(hashCode [this#]
(.hashCode ^Object (pyobj/hash-code this#)))
~@body)
{:type :pyobject})))

Expand Down
4 changes: 0 additions & 4 deletions src/libpython_clj/python/interop.clj
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,6 @@
(into-array Object [(str fieldname)])))


(defn create-python-type
[type-name module-name method-def-data])


(defn method-def-data-seq->method-def-ref
^PyMethodDef$ByReference [method-def-data-seq]
(when (seq method-def-data-seq)
Expand Down
25 changes: 25 additions & 0 deletions src/libpython_clj/python/object.clj
Original file line number Diff line number Diff line change
Expand Up @@ -1052,3 +1052,28 @@
:else
{:type (python-type pyobj)
:value (Pointer/nativeValue (jna/as-ptr pyobj))}))


(defn is-instance?
"Returns true if inst is an instance of type.
False otherwise."
[py-inst py-type]
(with-gil
(= 1 (libpy/PyObject_IsInstance (->python py-inst)
;;The type has to be a python type already.
py-type))))


(defn hash-code
^long [py-inst]
(with-gil
(long (libpy/PyObject_Hash (->python py-inst)))))


(defn equals?
"Returns true of the python equals operator returns 1."
[lhs rhs]
(with-gil
(= 1 (libpy/PyObject_RichCompareBool (->python lhs)
(->python rhs)
:py-eq))))
20 changes: 13 additions & 7 deletions test/libpython_clj/python_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,6 @@
(py/initialize!)


(defn crashit
[]
(let [test-data (py/->python (py/as-python [1 2]))
test-py-data (py/->py-tuple [test-data test-data])
_ :ignored]
:ok))


(deftest stdout-and-stderr
(is (= "hey\n" (with-out-str
Expand Down Expand Up @@ -294,3 +287,16 @@
(is (dfn/equals (dtt/->tensor [[1 2 3]
[4 5 6]])
(py/as-tensor ary-data))))))


(deftest python-tuple-equals
(testing "Python tuples have nice equal semantics."
(let [lhs (py/->py-tuple [1 2])
same (py/->py-tuple [1 2])
not-same (py/->py-tuple [3 4])]
(is (= lhs same))
(is (not= lhs not-same))
(is (= (.hashCode lhs)
(.hashCode same)))
(is (not= (.hashCode lhs)
(.hashCode not-same))))))

0 comments on commit 015af9c

Please sign in to comment.