From e3f8c0a481ce0e8441d4ce13388b30b37b2e669d Mon Sep 17 00:00:00 2001 From: Ben Dudson Date: Mon, 7 Oct 2019 23:13:25 +0100 Subject: [PATCH] Add support for RATIO, python Fraction types 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 #26 --- README.org | 30 +++++++++++++++--------------- py4cl.py | 13 +++++++++++++ src/writer.lisp | 9 +++++++++ tests/tests.lisp | 17 +++++++++++++++++ 4 files changed, 54 insertions(+), 15 deletions(-) diff --git a/README.org b/README.org index 17d33f75..eceb4348 100644 --- a/README.org +++ b/README.org @@ -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 diff --git a/py4cl.py b/py4cl.py index ad8feb1a..7a64a8b3 100644 --- a/py4cl.py +++ b/py4cl.py @@ -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 diff --git a/src/writer.lisp b/src/writer.lisp index 89caa59b..25c0fe0d 100644 --- a/src/writer.lisp +++ b/src/writer.lisp @@ -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 diff --git a/tests/tests.lisp b/tests/tests.lisp index 87f4b236..02dd0d8e 100644 --- a/tests/tests.lisp +++ b/tests/tests.lisp @@ -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))) +