Skip to content

Commit

Permalink
Add support for RATIO, python Fraction types
Browse files Browse the repository at this point in the history
If available, the `fractions` python module is imported and used
to handle RATIO types. If the module is not available (< 2.6) then
the ratio will be converted to a float.

Fixes issue bendudson#26
  • Loading branch information
bendudson committed Oct 7, 2019
1 parent 287a01f commit e3f8c0a
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 15 deletions.
30 changes: 15 additions & 15 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -113,23 +113,23 @@ Data is passed between python and lisp as text. The python function
reader; the lisp function =pythonize= outputs strings which can be
=eval='d in python. The following type conversions are done:

| Lisp type | Python type |
|-----------+---------------|
| NIL | None |
| integer | int |
| ratio | float |
| real | float |
| complex | complex float |
| string | str |
| hash map | dict |
| list | tuple |
| vector | list |
| array | NumPy array |
| symbol | Symbol class |
| function | function |
| Lisp type | Python type |
|-----------+--------------------|
| NIL | None |
| integer | int |
| ratio | fractions.Fraction |
| real | float |
| complex | complex float |
| string | str |
| hash map | dict |
| list | tuple |
| vector | list |
| array | NumPy array |
| symbol | Symbol class |
| function | function |

Note that python does not have all the numerical types which lisp has,
for example rational numbers or complex integers.
for example complex integers.

Because =python-eval= and =python-exec= evaluate strings as python
expressions, strings passed to them are not escaped or converted as
Expand Down
13 changes: 13 additions & 0 deletions py4cl.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,19 @@ def message_dispatch_loop():
except:
pass

# Handle fractions (RATIO type)
# Lisp will pass strings containing "_py4cl_fraction(n,d)"
# where n and d are integers.
try:
import fractions
eval_globals["_py4cl_fraction"] = fractions.Fraction

# Turn a Fraction into a Lisp RATIO
lispifiers[fractions.Fraction] = str
except:
# In python2, ensure that fractions are converted to floats
eval_globals["_py4cl_fraction"] = lambda a,b : float(a)/b

async_results = {} # Store for function results. Might be Exception
async_handle = itertools.count(0) # Running counter

Expand Down
9 changes: 9 additions & 0 deletions src/writer.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,15 @@ The lisp function is stored in the same object store as other objects."
(write-to-string (python-object-handle obj))
"]"))

(defmethod pythonize ((obj ratio))
"Handle ratios, using Python's Fraction if available"
(concatenate 'string
"_py4cl_fraction("
(pythonize (numerator obj))
","
(pythonize (denominator obj))
")"))

(defun stream-write-string (str stream)
"Write a string to a stream, putting the length first"
;; Convert the value to a string
Expand Down
17 changes: 17 additions & 0 deletions tests/tests.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -692,3 +692,20 @@ class Foo():
;; Check if no "residue" left

(assert-equalp 5 (py4cl:python-eval 5)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Rational numbers

(deftest ratios (pytests)
;; Ratios survive a round trip
(assert-equalp 1/2
(py4cl:python-eval 1/2))

;; Ratios (Fractions in python) can be manipulated
(assert-equalp 1/4
(py4cl:python-eval 1/2 "/" 2))

;; Complex ratios not supported in python so converts to floats
(assert-equality #'= #C(0.5 1.0)
(py4cl:python-eval #C(1 2) "*" 1/2)))

0 comments on commit e3f8c0a

Please sign in to comment.