From d79f1fdfb3b8dd92c908cc6ccd92cc3bd508521e Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Fri, 28 Apr 2017 13:16:29 +0200 Subject: [PATCH 01/22] more verbose doctest for debugging gen_to_integer --- cypari2/convert.pyx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cypari2/convert.pyx b/cypari2/convert.pyx index 11d782af..d90b2f69 100644 --- a/cypari2/convert.pyx +++ b/cypari2/convert.pyx @@ -129,11 +129,14 @@ cpdef gen_to_integer(Gen x): 5 >>> gen_to_integer(pari("Pol(42)")) 42 - >>> gen_to_integer(pari("x")) + >>> gen_to_integer(pari("u")) Traceback (most recent call last): ... - TypeError: unable to convert PARI object x of type t_POL to an integer - >>> gen_to_integer(pari("x + O(x^2)")) + TypeError: unable to convert PARI object u of type t_POL to an integer + >>> s = pari("x + O(x^2)") + >>> s + x + O(x^2) + >>> gen_to_integer(s) Traceback (most recent call last): ... TypeError: unable to convert PARI object x + O(x^2) of type t_SER to an integer From d55e015731082a51d6a45e97baed87e0feac80fa Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Fri, 28 Apr 2017 15:17:41 +0200 Subject: [PATCH 02/22] full Python2 & Python3 support --- .gitignore | 1 + autogen/args.py | 18 +- cypari2/closure.pyx | 5 +- cypari2/convert.pyx | 86 +++++--- cypari2/gen.pyx | 422 +++++++++++++++++++++----------------- cypari2/pari_instance.pyx | 53 ++--- setup.py | 6 + tests/rundoctest.py | 2 +- 8 files changed, 347 insertions(+), 246 deletions(-) diff --git a/.gitignore b/.gitignore index 4efdca2a..4edbf910 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ cypari2/gen.c cypari2/handle_error.c cypari2/pari_instance.c cypari2/stack.c +cypari2/system.pxi # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/autogen/args.py b/autogen/args.py index b7b4939e..4a2905df 100644 --- a/autogen/args.py +++ b/autogen/args.py @@ -12,6 +12,9 @@ # http://www.gnu.org/licenses/ #***************************************************************************** +import sys +PY_MAJOR_VERSION = sys.version_info.major + # Some replacements for reserved words replacements = {'char': 'character'} @@ -214,15 +217,22 @@ def _typerepr(self): return "str" def convert_code(self): if self.default is None: - s = " {name} = str({name})\n" - s += " cdef char* {tmp} = {name}\n" + if PY_MAJOR_VERSION == 2: + s = " {name} = str({name})\n" + else: + s = " {name} = {name}.encode('ascii') if isinstance({name}, str) else bytes({name})\n" + s += " cdef char* {tmp} = {name}\n" else: s = " cdef char* {tmp}\n" s += " if {name} is None:\n" s += " {tmp} = {default}\n" s += " else:\n" - s += " {name} = bytes({name})\n" - s += " {tmp} = {name}\n" + if PY_MAJOR_VERSION == 2: + s += " {name} = bytes({name})\n" + s += " {tmp} = {name}\n" + else: + s += " {name} = {name}.encode('ascii') if isinstance({name}, str) else bytes({name})\n" + s += " {tmp} = {name}\n" return s.format(name=self.name, tmp=self.tmpname, default=self.default) def call_code(self): return self.tmpname diff --git a/cypari2/closure.pyx b/cypari2/closure.pyx index f788776d..c2544891 100644 --- a/cypari2/closure.pyx +++ b/cypari2/closure.pyx @@ -138,7 +138,8 @@ cpdef Gen objtoclosure(f): Examples: >>> from cypari2.closure import objtoclosure - >>> mul = objtoclosure(lambda i,j: i*j) + >>> def pymul(i,j): return i*j + >>> mul = objtoclosure(pymul) >>> mul (v1,v2,v3,v4,v5)->call_python(v1,v2,v3,v4,v5,...) >>> mul.type() @@ -155,7 +156,7 @@ cpdef Gen objtoclosure(f): >>> mul(4) Traceback (most recent call last): ... - TypeError: () takes exactly 2 arguments (1 given) + TypeError: pymul() missing 1 required positional argument: 'j' >>> mul(None, None) Traceback (most recent call last): ... diff --git a/cypari2/convert.pyx b/cypari2/convert.pyx index d90b2f69..66cfe6a4 100644 --- a/cypari2/convert.pyx +++ b/cypari2/convert.pyx @@ -55,6 +55,7 @@ from .stack cimport new_gen cdef extern from *: Py_ssize_t* Py_SIZE_PTR "&Py_SIZE"(object) +include "system.pxi" #################################### # Integers @@ -73,27 +74,35 @@ cpdef integer_to_gen(x): >>> a = integer_to_gen(int(12345)); a; type(a) 12345 <... 'cypari2.gen.Gen'> - >>> a = integer_to_gen(long(12345)); a; type(a) - 12345 - <... 'cypari2.gen.Gen'> >>> integer_to_gen(float(12345)) Traceback (most recent call last): ... TypeError: integer_to_gen() needs an int or long argument, not float + >>> integer_to_gen(2**100) + 1267650600228229401496703205376 Tests: - >>> for i in range(10000): - ... x = 3**i - ... if pari(long(x)) != pari(x): - ... print(x) + >>> import sys + >>> if sys.version_info.major == 2: + ... assert integer_to_gen(long(12345)) == 12345 + ... for i in range(10000): + ... x = 3**i + ... if pari(long(x)) != pari(x): + ... print(x) """ - if isinstance(x, int): - sig_on() - return new_gen(stoi(PyInt_AS_LONG(x))) - elif isinstance(x, long): - sig_on() - return new_gen(PyLong_AsGEN(x)) + IF PY_MAJOR_VERSION == 2: + if isinstance(x, int): + sig_on() + return new_gen(stoi(PyInt_AS_LONG(x))) + elif isinstance(x, long): + sig_on() + return new_gen(PyLong_AsGEN(x)) + ELSE: + if isinstance(x, int): + sig_on() + return new_gen(PyLong_AsGEN(x)) + raise TypeError("integer_to_gen() needs an int or long argument, not {}".format(type(x).__name__)) cpdef gen_to_integer(Gen x): @@ -114,9 +123,8 @@ cpdef gen_to_integer(Gen x): >>> a = gen_to_integer(pari("12345")); a; type(a) 12345 <... 'int'> - >>> a = gen_to_integer(pari("10^30")); a; type(a) - 1000000000000000000000000000000L - <... 'long'> + >>> gen_to_integer(pari("10^30")) == 10**30 + True >>> gen_to_integer(pari("19/5")) 3 >>> gen_to_integer(pari("1 + 0.0*I")) @@ -147,14 +155,17 @@ cpdef gen_to_integer(Gen x): Tests: - >>> for i in range(10000): - ... x = 3**i - ... if long(pari(x)) != long(x): - ... print(x) - >>> gen_to_integer(pari("1.0 - 2^64")) - -18446744073709551615L - >>> gen_to_integer(pari("1 - 2^64")) - -18446744073709551615L + >>> gen_to_integer(pari("1.0 - 2^64")) == -18446744073709551615 + True + >>> gen_to_integer(pari("1 - 2^64")) == -18446744073709551615 + True + >>> import sys + >>> if sys.version_info.major == 2: + ... for i in range(10000): + ... x = 3**i + ... if long(pari(x)) != long(x): + ... print(x) + Check some corner cases: @@ -214,9 +225,14 @@ cdef GEN gtoi(GEN g0) except NULL: sig_error() sig_off() except RuntimeError: - raise TypeError(stack_sprintf( + IF PY_MAJOR_VERSION == 2: + raise TypeError(stack_sprintf( "unable to convert PARI object %Ps of type %s to an integer", g0, type_name(typ(g0)))) + ELSE: + s = bytes(stack_sprintf("unable to convert PARI object %Ps of type %s to an integer", + g0, type_name(typ(g0)))) + raise TypeError(s.decode('ascii')) return g @@ -462,9 +478,10 @@ cpdef gen_to_python(Gen z): >>> type(a) <... 'int'> - >>> a = gen_to_python(pari('3^50')) - >>> type(a) - <... 'long'> + >>> gen_to_python(pari('3^50')) == 3**50 + True + >>> type(gen_to_python(pari('3^50'))) == type(3**50) + True Converting rational numbers: @@ -527,21 +544,21 @@ cpdef gen_to_python(Gen z): [1, 2, 3] >>> type(a1) <... 'list'> - >>> map(type, a1) + >>> list(map(type, a1)) [<... 'int'>, <... 'int'>, <... 'int'>] >>> a2 = gen_to_python(z2); a2 [1, 3.4, [-5, 2], inf] >>> type(a2) <... 'list'> - >>> map(type, a2) + >>> list(map(type, a2)) [<... 'int'>, <... 'float'>, <... 'list'>, <... 'float'>] >>> a3 = gen_to_python(z3); a3 [1, 5.2] >>> type(a3) <... 'list'> - >>> map(type, a3) + >>> list(map(type, a3)) [<... 'int'>, <... 'float'>] Converting matrices: @@ -598,7 +615,7 @@ cpdef gen_to_python(Gen z): elif t == t_VEC or t == t_COL: return [gen_to_python(x) for x in z.python_list()] elif t == t_VECSMALL: - return z.python_list_small() + return z.python_list_small() elif t == t_MAT: nc = lg(g)-1 nr = 0 if nc == 0 else lg(gel(g,1))-1 @@ -609,6 +626,9 @@ cpdef gen_to_python(Gen z): else: return -INFINITY elif t == t_STR: - return str(z) + IF PY_MAJOR_VERSION == 2: + return GSTR(g) + ELSE: + return bytes(GSTR(g)).decode('ascii') else: raise NotImplementedError("conversion not implemented for {}".format(z.type())) diff --git a/cypari2/gen.pyx b/cypari2/gen.pyx index c1f7757b..cd83536d 100644 --- a/cypari2/gen.pyx +++ b/cypari2/gen.pyx @@ -39,6 +39,9 @@ AUTHORS: - Luca De Feo (2016-09-06): Separate Sage-specific components from generic C-interface in ``Pari`` (:trac:`20241`) + +- Vincent Delecroix (2017-04-29): Python 3 support and doctest + conversion """ #***************************************************************************** @@ -48,6 +51,7 @@ AUTHORS: # Copyright (C) 2010 Robert Bradshaw # Copyright (C) 2010-2016 Jeroen Demeyer # Copyright (C) 2016 Luca De Feo +# Copyright (C) 2017 Vincent Delecroix # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -82,6 +86,7 @@ from .closure cimport objtoclosure include 'auto_gen.pxi' +include 'system.pxi' @cython.final @@ -155,9 +160,12 @@ cdef class Gen(Gen_auto): sig_unblock() sig_off() - s = str(c) + s = bytes(c) pari_free(c) - return s + IF PY_MAJOR_VERSION == 2: + return s + ELSE: + return s.decode('ascii') def __str__(self): """ @@ -172,16 +180,19 @@ cdef class Gen(Gen_auto): >>> from cypari2 import Pari >>> pari = Pari() - >>> print(pari('vector(5,i,i)')) - [1, 2, 3, 4, 5] - >>> print(pari('[1,2;3,4]')) - [1, 2; 3, 4] - >>> print(pari('Str(hello)')) - hello + >>> str(pari('vector(5,i,i)')) + '[1, 2, 3, 4, 5]' + >>> str(pari('[1,2;3,4]')) + '[1, 2; 3, 4]' + >>> str(pari('Str(hello)')) + 'hello' """ # Use __repr__ except for strings if typ(self.g) == t_STR: - return GSTR(self.g) + IF PY_MAJOR_VERSION == 2: + return GSTR(self.g) + ELSE: + return bytes(GSTR(self.g)).decode('ascii') return repr(self) def __hash__(self): @@ -319,7 +330,10 @@ cdef class Gen(Gen_auto): return (x[i] for i in range(1, lg(x))) elif t == t_STR: # Special case: convert to str - return iter(GSTR(self.g)) + IF PY_MAJOR_VERSION == 2: + return iter(GSTR(self.g)) + ELSE: + return iter(bytes(GSTR(self.g)).decode('ascii')) else: v = self.Vec() @@ -344,7 +358,7 @@ cdef class Gen(Gen_auto): >>> type(L) <... 'list'> >>> type(L[0]) - + <... 'cypari2.gen.Gen'> For polynomials, list() returns the list of coefficients: @@ -1326,7 +1340,7 @@ cdef class Gen(Gen_auto): >>> s [1, 0] >>> type(s[0]) - + <... 'cypari2.gen.Gen'> >>> s = pari(range(20)) ; s [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] >>> s[0:10:2] = range(50,55) ; s @@ -1342,7 +1356,7 @@ cdef class Gen(Gen_auto): >>> v [20, 21, 22, 23, 24, 25, 26, 27, 28, 29] >>> type(v[0]) - + <... 'cypari2.gen.Gen'> """ cdef Py_ssize_t i, j, step cdef Gen x = objtogen(y) @@ -1485,137 +1499,170 @@ cdef class Gen(Gen_auto): sig_off() return r - def __cmp__(self, Gen other): - """ - Compare ``left`` and ``right``. - - This uses PARI's ``cmp_universal()`` routine, which defines - a total ordering on the set of all PARI objects (up to the - indistinguishability relation given by ``gidentical()``). - - .. WARNING:: - - This comparison is only mathematically meaningful when - comparing 2 integers. In particular, when comparing - rationals or reals, this does not correspond to the natural - ordering. - - Examples: - - >>> from cypari2 import Pari - >>> pari = Pari() - - >>> cmp(pari(5), 5) - 0 - >>> cmp(pari(5), 10) - -1 - >>> cmp(pari(2.5), None) - 1 - >>> cmp(pari(3), pari(3)) - 0 - >>> cmp(pari('x^2 + 1'), pari('I-1')) - 1 - >>> I = pari('I') - >>> cmp(I, I) - 0 - - Beware when comparing rationals or reals: - - >>> cmp(pari('2/3'), pari('2/5')) - -1 - >>> two = pari('2.000000000000000000000000') - >>> cmp(two, pari(1.0)) - 1 - >>> cmp(two, pari(2.0)) - 1 - >>> cmp(two, pari(3.0)) - 1 - - Since :trac:`17026`, different elements with the same string - representation can be distinguished by ``cmp()``: - - >>> a = pari(0); a - 0 - >>> b = pari("0*ffgen(ffinit(29, 10))"); b - 0 - >>> cmp(a, b) - -1 - - >>> x = pari("x"); x - x - >>> y = pari("ffgen(ffinit(3, 5))"); y - x - >>> cmp(x, y) - 1 - """ - sig_on() - cdef int r = cmp_universal(self.g, other.g) - sig_off() - return r + IF PY_MAJOR_VERSION == 2: + def __cmp__(self, Gen other): + """ + Compare ``left`` and ``right``. + + This uses PARI's ``cmp_universal()`` routine, which defines + a total ordering on the set of all PARI objects (up to the + indistinguishability relation given by ``gidentical()``). + + .. WARNING:: + + This comparison is only mathematically meaningful when + comparing 2 integers. In particular, when comparing + rationals or reals, this does not correspond to the natural + ordering. + + Examples: + + >>> from cypari2 import Pari + >>> pari = Pari() + + >>> cmp(pari(5), 5) + 0 + >>> cmp(pari(5), 10) + -1 + >>> cmp(pari(2.5), None) + 1 + >>> cmp(pari(3), pari(3)) + 0 + >>> cmp(pari('x^2 + 1'), pari('I-1')) + 1 + >>> I = pari('I') + >>> cmp(I, I) + 0 + + Beware when comparing rationals or reals: + + >>> cmp(pari('2/3'), pari('2/5')) + -1 + >>> two = pari('2.000000000000000000000000') + >>> cmp(two, pari(1.0)) + 1 + >>> cmp(two, pari(2.0)) + 1 + >>> cmp(two, pari(3.0)) + 1 + + Since :trac:`17026`, different elements with the same string + representation can be distinguished by ``cmp()``: + + >>> a = pari(0); a + 0 + >>> b = pari("0*ffgen(ffinit(29, 10))"); b + 0 + >>> cmp(a, b) + -1 + + >>> x = pari("x"); x + x + >>> y = pari("ffgen(ffinit(3, 5))"); y + x + >>> cmp(x, y) + 1 + """ + sig_on() + cdef int r = cmp_universal(self.g, other.g) + sig_off() + return r def __copy__(self): sig_on() return new_gen(self.g) - def __hex__(self): - """ - Return the hexadecimal digits of self in lower case. - - Examples: - - >>> from cypari2 import Pari - >>> pari = Pari() - - >>> print(hex(pari(0))) - 0 - >>> print(hex(pari(15))) - f - >>> print(hex(pari(16))) - 10 - >>> print(hex(pari(16938402384092843092843098243))) - 36bb1e3929d1a8fe2802f083 - >>> print(hex(long(16938402384092843092843098243))) - 0x36bb1e3929d1a8fe2802f083L - >>> print(hex(pari(-16938402384092843092843098243))) - -36bb1e3929d1a8fe2802f083 - """ - cdef GEN x - cdef long lx - cdef long *xp - cdef long w - cdef char *s - cdef char *sp - cdef char *hexdigits - hexdigits = "0123456789abcdef" - cdef int i, j - cdef int size - x = self.g - if typ(x) != t_INT: - raise TypeError("gen must be of PARI type t_INT") - if not signe(x): - return "0" - lx = lgefint(x)-2 # number of words - size = lx*2*sizeof(long) - s = sig_malloc(size+2) # 1 char for sign, 1 char for '\0' - sp = s + size+1 - sp[0] = 0 - xp = int_LSW(x) - for i from 0 <= i < lx: - w = xp[0] - for j from 0 <= j < 2*sizeof(long): - sp = sp-1 - sp[0] = hexdigits[w & 15] - w = w>>4 - xp = int_nextW(xp) - # remove leading zeros! - while sp[0] == c'0': - sp = sp+1 - if signe(x) < 0: - sp = sp-1 - sp[0] = c'-' - k = sp - sig_free(s) - return k + # NOTE: In Python3, oct(xxx) calls __index__ + IF PY_MAJOR_VERSION == 2: + def __oct__(self): + """ + Return the octal digits of self in lower case. + """ + cdef GEN x + cdef long lx + cdef long *xp + cdef long w + cdef char *s + cdef char *sp + cdef char *octdigits = "01234567" + cdef int i, j + cdef int size + x = self.g + if typ(x) != t_INT: + raise TypeError("gen must be of PARI type t_INT") + if not signe(x): + return "0" + lx = lgefint(x) - 2 # number of words + size = lx * 4 * sizeof(long) + s = sig_malloc(size+3) # 1 char for sign, 1 char for 0, 1 char for '\0' + sp = s + size + 3 + sp[0] = 0 + xp = int_LSW(x) + for i from 0 <= i < lx: + w = xp[0] + for j in range(4*sizeof(long)): + sp -= 1 + sp[0] = octdigits[w & 7] + w >>= 3 + xp = int_nextW(xp) + # remove leading zeros! + while sp[0] == c'0': + sp += 1 + sp -= 1 + sp[0] = c'0' + if signe(x) < 0: + sp -= 1 + sp[0] = c'-' + k = sp + sig_free(s) + return k + + # NOTE: In Python3, hex(xxx) calls __index__ + IF PY_MAJOR_VERSION == 2: + def __hex__(self): + """ + Return the hexadecimal digits of self in lower case. + """ + cdef GEN x + cdef long lx + cdef long *xp + cdef long w + cdef char *s + cdef char *sp + cdef char *hexdigits = "0123456789abcdef" + cdef int i, j + cdef int size + x = self.g + if typ(x) != t_INT: + raise TypeError("gen must be of PARI type t_INT") + if not signe(x): + return "0x0" + lx = lgefint(x) - 2 # number of words + size = lx*2*sizeof(long) + s = sig_malloc(size+4) # 1 char for sign, 2 chars for 0x, 1 char for '\0' + sp = s + size + 4 + sp[0] = 0 + xp = int_LSW(x) + for i from 0 <= i < lx: + w = xp[0] + for j in range(2*sizeof(long)): + sp -= 1 + sp[0] = hexdigits[w & 15] + w >>= 4 + xp = int_nextW(xp) + # remove leading zeros! + while sp[0] == c'0': + sp = sp + 1 + sp -= 1 + sp[0] = 'x' + sp -= 1 + sp[0] = '0' + if signe(x) < 0: + sp -= 1 + sp[0] = c'-' + k = sp + sig_free(s) + return k def __int__(self): """ @@ -1635,10 +1682,10 @@ cdef class Gen(Gen_auto): 10 >>> int(pari(-10)) -10 - >>> int(pari(123456789012345678901234567890)) - 123456789012345678901234567890L - >>> int(pari(-123456789012345678901234567890)) - -123456789012345678901234567890L + >>> int(pari(123456789012345678901234567890)) == 123456789012345678901234567890 + True + >>> int(pari(-123456789012345678901234567890)) == -123456789012345678901234567890 + True >>> int(pari(2**31-1)) 2147483647 >>> int(pari(-2**31)) @@ -1678,6 +1725,14 @@ cdef class Gen(Gen_auto): Traceback (most recent call last): ... TypeError: cannot coerce 2.50000000000000 (type t_REAL) to integer + + >>> for i in [0,1,2,15,16,17,1213051238]: + ... assert bin(pari(i)) == bin(i) + ... assert bin(pari(-i)) == bin(-i) + ... assert oct(pari(i)) == oct(i) + ... assert oct(pari(-i)) == oct(-i) + ... assert hex(pari(i)) == hex(i) + ... assert hex(pari(-i)) == hex(-i) """ if typ(self.g) != t_INT: raise TypeError(f"cannot coerce {self!r} (type {self.type()}) to integer") @@ -1728,7 +1783,7 @@ cdef class Gen(Gen_auto): >>> w [1, 2, 3, 10, 102, 10] >>> type(w[0]) - + <... 'cypari2.gen.Gen'> >>> pari("[1,2,3]").python_list() [1, 2, 3] @@ -1775,39 +1830,40 @@ cdef class Gen(Gen_auto): from sage.libs.pari.convert_sage import gen_to_sage return gen_to_sage(self, locals) - def __long__(self): - """ - Convert ``self`` to a Python ``long``. - - Examples: - - >>> from cypari2 import Pari - >>> pari = Pari() - - >>> long(pari(0)) - 0L - >>> long(pari(10)) - 10L - >>> long(pari(-10)) - -10L - >>> long(pari(123456789012345678901234567890)) - 123456789012345678901234567890L - >>> long(pari(-123456789012345678901234567890)) - -123456789012345678901234567890L - >>> long(pari(2**31-1)) - 2147483647L - >>> long(pari(-2**31)) - -2147483648L - >>> long(pari("Pol(10)")) - 10L - >>> long(pari("Mod(2, 7)")) - 2L - """ - x = gen_to_integer(self) - if isinstance(x, long): - return x - else: - return long(x) + IF PY_MAJOR_VERSION == 2: + def __long__(self): + """ + Convert ``self`` to a Python ``long``. + + Examples: + + >>> from cypari2 import Pari + >>> pari = Pari() + + >>> long(pari(0)) + 0L + >>> long(pari(10)) + 10L + >>> long(pari(-10)) + -10L + >>> long(pari(123456789012345678901234567890)) + 123456789012345678901234567890L + >>> long(pari(-123456789012345678901234567890)) + -123456789012345678901234567890L + >>> long(pari(2**31-1)) + 2147483647L + >>> long(pari(-2**31)) + -2147483648L + >>> long(pari("Pol(10)")) + 10L + >>> long(pari("Mod(2, 7)")) + 2L + """ + x = gen_to_integer(self) + if isinstance(x, long): + return x + else: + return long(x) def __float__(self): """ @@ -3423,7 +3479,7 @@ cdef class Gen(Gen_auto): >>> v = e.ellaplist(10); v [-2, -1, 1, -2] >>> type(v) - + <... 'cypari2.gen.Gen'> >>> v.type() 't_VEC' >>> e.ellan(10) @@ -3439,7 +3495,7 @@ cdef class Gen(Gen_auto): >>> v = e.ellaplist(1) >>> v, type(v) - ([], ) + ([], <... 'cypari2.gen.Gen'>) >>> v = e.ellaplist(1, python_ints=True) >>> v, type(v) ([], <... 'list'>) @@ -4183,8 +4239,8 @@ cdef class Gen(Gen_auto): return new_gen(gsubst(self.g, varn(self.g), t0.g)) # Call substvec() using **kwds - vstr = kwds.keys() # Variables as Python strings - t0 = objtogen(kwds.values()) # Replacements + vstr = list(kwds.keys()) # Variables as Python strings + t0 = objtogen(list(kwds.values())) # Replacements sig_on() cdef GEN v = cgetg(nkwds+1, t_VEC) # Variables as PARI polynomials @@ -4941,7 +4997,7 @@ cpdef Gen objtogen(s): >>> pari(int(-5)) -5 - >>> pari(long(2**150)) + >>> pari(2**150) 1427247692705959881058285969449495136382746624 >>> import math >>> pari(math.pi) @@ -4962,7 +5018,7 @@ cpdef Gen objtogen(s): >>> pari("dummy = 0; kill(dummy)") >>> type(pari("dummy = 0; kill(dummy)")) - + <... 'NoneType'> Tests: diff --git a/cypari2/pari_instance.pyx b/cypari2/pari_instance.pyx index 1c9daded..ddcae716 100644 --- a/cypari2/pari_instance.pyx +++ b/cypari2/pari_instance.pyx @@ -50,9 +50,9 @@ Examples: Arithmetic operations cause all arguments to be converted to PARI: >>> type(pari(1) + 1) - +<... 'cypari2.gen.Gen'> >>> type(1 + pari(1)) - +<... 'cypari2.gen.Gen'> Guide to real precision in the PARI interface ============================================= @@ -239,6 +239,8 @@ from .convert cimport new_gen_from_double from .handle_error cimport _pari_init_error_handling from .closure cimport _pari_init_closure +include "system.pxi" + # Default precision (in PARI words) for the PARI library interface, # when no explicit precision is given and the inputs are exact. cdef long prec = prec_bits_to_words(53) @@ -384,22 +386,28 @@ def prec_words_to_dec(long prec_in_words): # Callbacks from PARI to print stuff using sys.stdout.write() instead # of C library functions like puts(). -cdef PariOUT sage_pariOut +cdef PariOUT python_pariOut -cdef void sage_putchar(char c): +cdef void python_putchar(char c): cdef char s[2] s[0] = c s[1] = 0 - sys.stdout.write(s) + IF PY_MAJOR_VERSION == 2: + sys.stdout.write(s) + ELSE: + sys.stdout.write(bytes(s).decode('ascii')) # Let PARI think the last character was a newline, # so it doesn't print one when an error occurs. pari_set_last_newline(1) -cdef void sage_puts(const char* s): - sys.stdout.write(s) +cdef void python_puts(const char* s): + IF PY_MAJOR_VERSION == 2: + sys.stdout.write(s) + ELSE: + sys.stdout.write(bytes(s).decode('ascii')) pari_set_last_newline(1) -cdef void sage_flush(): +cdef void python_flush(): sys.stdout.flush() include 'auto_instance.pxi' @@ -415,6 +423,8 @@ cdef class Pari(Pari_auto): >>> from cypari2.pari_instance import Pari >>> Pari.__new__(Pari) Interface to the PARI C library + >>> pari = Pari() + >>> pari("print('hello')") """ # PARI is already initialized, nothing to do... if avma: @@ -434,10 +444,10 @@ cdef class Pari(Pari_auto): # Set printing functions global pariOut, pariErr - pariOut = &sage_pariOut - pariOut.putch = sage_putchar - pariOut.puts = sage_puts - pariOut.flush = sage_flush + pariOut = &python_pariOut + pariOut.putch = python_putchar + pariOut.puts = python_puts + pariOut.flush = python_flush # Use 53 bits as default precision self.set_real_precision_bits(53) @@ -579,16 +589,6 @@ cdef class Pari(Pari_auto): Print the internal PARI variables ``top`` (top of stack), ``avma`` (available memory address, think of this as the stack pointer), ``bot`` (bottom of stack). - - Examples: - - >>> import cypari2 - >>> pari = cypari2.Pari() - >>> pari.debugstack() # random - top = 0x60b2c60 - avma = 0x5875c38 - bot = 0x57295e0 - size = 1000000 """ # We deliberately use low-level functions to minimize the # chances that something goes wrong here (for example, if we @@ -1381,7 +1381,14 @@ cdef long get_var(v) except -2: return varno if v == -1: return -1 - cdef bytes s = bytes(v) + cdef bytes s + IF PY_MAJOR_VERSION == 2: + s = bytes(v) + ELSE: + if isinstance(v, str): + s = (v).encode('ascii') + else: + s = bytes(v) sig_on() varno = fetch_user_var(s) sig_off() diff --git a/setup.py b/setup.py index da9935a6..13544801 100755 --- a/setup.py +++ b/setup.py @@ -28,6 +28,12 @@ def finalize_options(self): from autogen import rebuild rebuild() + # Write compilation constants in system.pxi + import sys + with open('cypari2/system.pxi', 'w') as output: + output.write('DEF PY_MAJOR_VERSION = %d\n' % sys.version_info.major) + output.write('DEF PY_MINOR_VERSION = %d\n' % sys.version_info.minor) + self.distribution.ext_modules[:] = cythonize( self.distribution.ext_modules, include_path=sys.path) _build_ext.finalize_options(self) diff --git a/tests/rundoctest.py b/tests/rundoctest.py index 909d9395..25e110bc 100644 --- a/tests/rundoctest.py +++ b/tests/rundoctest.py @@ -11,7 +11,7 @@ print("="*80) print("Testing {}".format(mod.__name__)) - test = doctest.testmod(mod, optionflags=doctest.ELLIPSIS|doctest.REPORT_NDIFF) + test = doctest.testmod(mod, optionflags=doctest.ELLIPSIS|doctest.REPORT_NDIFF|doctest.IGNORE_EXCEPTION_DETAIL) failed += test.failed attempted += test.attempted From d7b7a3ee16032783a1e86b8445eddaf89352ad0c Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Fri, 12 May 2017 16:55:00 +0200 Subject: [PATCH 03/22] restore a space --- cypari2/convert.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypari2/convert.pyx b/cypari2/convert.pyx index 66cfe6a4..1985ebe6 100644 --- a/cypari2/convert.pyx +++ b/cypari2/convert.pyx @@ -615,7 +615,7 @@ cpdef gen_to_python(Gen z): elif t == t_VEC or t == t_COL: return [gen_to_python(x) for x in z.python_list()] elif t == t_VECSMALL: - return z.python_list_small() + return z.python_list_small() elif t == t_MAT: nc = lg(g)-1 nr = 0 if nc == 0 else lg(gel(g,1))-1 From 418cf4021caac5358e575fed8b1c87ea3c883b3d Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Mon, 15 May 2017 08:27:49 +0200 Subject: [PATCH 04/22] use lambda in closure --- cypari2/closure.pyx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cypari2/closure.pyx b/cypari2/closure.pyx index c2544891..541cf5bc 100644 --- a/cypari2/closure.pyx +++ b/cypari2/closure.pyx @@ -138,8 +138,7 @@ cpdef Gen objtoclosure(f): Examples: >>> from cypari2.closure import objtoclosure - >>> def pymul(i,j): return i*j - >>> mul = objtoclosure(pymul) + >>> mul = objtoclosure(lambda i,j: i*j) >>> mul (v1,v2,v3,v4,v5)->call_python(v1,v2,v3,v4,v5,...) >>> mul.type() From 001d770159dd661e64689d809e2c79cae08db8e7 Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Tue, 6 Jun 2017 19:31:29 +0300 Subject: [PATCH 05/22] use compile_time_env option in cythonize for PY_MAJOR_VERSION --- cypari2/convert.pyx | 2 -- cypari2/gen.pyx | 3 --- cypari2/pari_instance.pyx | 2 -- setup.py | 9 ++------- 4 files changed, 2 insertions(+), 14 deletions(-) diff --git a/cypari2/convert.pyx b/cypari2/convert.pyx index 1985ebe6..a528b227 100644 --- a/cypari2/convert.pyx +++ b/cypari2/convert.pyx @@ -55,8 +55,6 @@ from .stack cimport new_gen cdef extern from *: Py_ssize_t* Py_SIZE_PTR "&Py_SIZE"(object) -include "system.pxi" - #################################### # Integers #################################### diff --git a/cypari2/gen.pyx b/cypari2/gen.pyx index cd83536d..b6d96f21 100644 --- a/cypari2/gen.pyx +++ b/cypari2/gen.pyx @@ -84,10 +84,7 @@ from .pari_instance cimport (prec_bits_to_words, prec_words_to_bits, from .stack cimport new_gen, new_gen_noclear, clear_stack from .closure cimport objtoclosure - include 'auto_gen.pxi' -include 'system.pxi' - @cython.final cdef class Gen(Gen_auto): diff --git a/cypari2/pari_instance.pyx b/cypari2/pari_instance.pyx index ddcae716..ecba57e0 100644 --- a/cypari2/pari_instance.pyx +++ b/cypari2/pari_instance.pyx @@ -239,8 +239,6 @@ from .convert cimport new_gen_from_double from .handle_error cimport _pari_init_error_handling from .closure cimport _pari_init_closure -include "system.pxi" - # Default precision (in PARI words) for the PARI library interface, # when no explicit precision is given and the inputs are exact. cdef long prec = prec_bits_to_words(53) diff --git a/setup.py b/setup.py index 13544801..133dec12 100755 --- a/setup.py +++ b/setup.py @@ -28,14 +28,9 @@ def finalize_options(self): from autogen import rebuild rebuild() - # Write compilation constants in system.pxi - import sys - with open('cypari2/system.pxi', 'w') as output: - output.write('DEF PY_MAJOR_VERSION = %d\n' % sys.version_info.major) - output.write('DEF PY_MINOR_VERSION = %d\n' % sys.version_info.minor) - self.distribution.ext_modules[:] = cythonize( - self.distribution.ext_modules, include_path=sys.path) + self.distribution.ext_modules, include_path=sys.path, + compile_time_env={'PY_MAJOR_VERSION': sys.version_info.major}) _build_ext.finalize_options(self) From 1ac5302a207106b746c3b9a48d2b9aa035a2e46e Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Tue, 6 Jun 2017 19:54:10 +0300 Subject: [PATCH 06/22] remove system.pxi from gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4edbf910..4efdca2a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,6 @@ cypari2/gen.c cypari2/handle_error.c cypari2/pari_instance.c cypari2/stack.c -cypari2/system.pxi # Byte-compiled / optimized / DLL files __pycache__/ From e94b0c7414553d7884b9202d429a1354fcf091ea Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Tue, 6 Jun 2017 23:35:55 +0300 Subject: [PATCH 07/22] better handling of strings (Py2/Py3) --- autogen/args.py | 15 ++--------- cypari2/convert.pyx | 14 +++-------- cypari2/gen.pyx | 20 ++++++--------- cypari2/pari_instance.pyx | 25 +++++++----------- cypari2/string_utils.pyx | 53 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 52 deletions(-) create mode 100644 cypari2/string_utils.pyx diff --git a/autogen/args.py b/autogen/args.py index 4a2905df..3db11ab1 100644 --- a/autogen/args.py +++ b/autogen/args.py @@ -12,9 +12,6 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -import sys -PY_MAJOR_VERSION = sys.version_info.major - # Some replacements for reserved words replacements = {'char': 'character'} @@ -217,22 +214,14 @@ def _typerepr(self): return "str" def convert_code(self): if self.default is None: - if PY_MAJOR_VERSION == 2: - s = " {name} = str({name})\n" - else: - s = " {name} = {name}.encode('ascii') if isinstance({name}, str) else bytes({name})\n" + s = " {name} = to_bytes({name})\n" s += " cdef char* {tmp} = {name}\n" else: s = " cdef char* {tmp}\n" s += " if {name} is None:\n" s += " {tmp} = {default}\n" s += " else:\n" - if PY_MAJOR_VERSION == 2: - s += " {name} = bytes({name})\n" - s += " {tmp} = {name}\n" - else: - s += " {name} = {name}.encode('ascii') if isinstance({name}, str) else bytes({name})\n" - s += " {tmp} = {name}\n" + s += " {tmp} = to_bytes({name})\n" return s.format(name=self.name, tmp=self.tmpname, default=self.default) def call_code(self): return self.tmpname diff --git a/cypari2/convert.pyx b/cypari2/convert.pyx index a528b227..a17b24b8 100644 --- a/cypari2/convert.pyx +++ b/cypari2/convert.pyx @@ -51,6 +51,7 @@ from libc.math cimport INFINITY from .paridecl cimport * from .stack cimport new_gen +from .string_utils cimport to_string cdef extern from *: Py_ssize_t* Py_SIZE_PTR "&Py_SIZE"(object) @@ -223,14 +224,10 @@ cdef GEN gtoi(GEN g0) except NULL: sig_error() sig_off() except RuntimeError: - IF PY_MAJOR_VERSION == 2: - raise TypeError(stack_sprintf( + s = to_string(stack_sprintf( "unable to convert PARI object %Ps of type %s to an integer", g0, type_name(typ(g0)))) - ELSE: - s = bytes(stack_sprintf("unable to convert PARI object %Ps of type %s to an integer", - g0, type_name(typ(g0)))) - raise TypeError(s.decode('ascii')) + raise TypeError(s) return g @@ -624,9 +621,6 @@ cpdef gen_to_python(Gen z): else: return -INFINITY elif t == t_STR: - IF PY_MAJOR_VERSION == 2: - return GSTR(g) - ELSE: - return bytes(GSTR(g)).decode('ascii') + return to_string( GSTR(g)) else: raise NotImplementedError("conversion not implemented for {}".format(z.type())) diff --git a/cypari2/gen.pyx b/cypari2/gen.pyx index b6d96f21..9884a670 100644 --- a/cypari2/gen.pyx +++ b/cypari2/gen.pyx @@ -76,6 +76,7 @@ include "cysignals/memory.pxi" include "cysignals/signals.pxi" from .paridecl cimport * +from .string_utils cimport to_string, to_bytes from .paripriv cimport * from .convert cimport (integer_to_gen, gen_to_integer, new_gen_from_double, new_t_COMPLEX_from_double) @@ -159,10 +160,7 @@ cdef class Gen(Gen_auto): s = bytes(c) pari_free(c) - IF PY_MAJOR_VERSION == 2: - return s - ELSE: - return s.decode('ascii') + return to_string(s) def __str__(self): """ @@ -186,10 +184,8 @@ cdef class Gen(Gen_auto): """ # Use __repr__ except for strings if typ(self.g) == t_STR: - IF PY_MAJOR_VERSION == 2: - return GSTR(self.g) - ELSE: - return bytes(GSTR(self.g)).decode('ascii') + # CHANGED + return GSTR(self.g) return repr(self) def __hash__(self): @@ -327,10 +323,8 @@ cdef class Gen(Gen_auto): return (x[i] for i in range(1, lg(x))) elif t == t_STR: # Special case: convert to str - IF PY_MAJOR_VERSION == 2: - return iter(GSTR(self.g)) - ELSE: - return iter(bytes(GSTR(self.g)).decode('ascii')) + # CHANGED + return iter( GSTR(self.g)) else: v = self.Vec() @@ -5062,7 +5056,7 @@ cpdef Gen objtogen(s): # isinstance(s, (unicode, bytes)) if PyUnicode_Check(s) | PyBytes_Check(s): sig_on() - g = gp_read_str(s) + g = gp_read_str(to_bytes(s)) if g == gnil: clear_stack() return None diff --git a/cypari2/pari_instance.pyx b/cypari2/pari_instance.pyx index ecba57e0..a19d5a46 100644 --- a/cypari2/pari_instance.pyx +++ b/cypari2/pari_instance.pyx @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ PARI C-library interface @@ -231,6 +232,7 @@ import sys from libc.stdio cimport * cimport cython +from .string_utils cimport to_string, to_bytes from .paridecl cimport * from .paripriv cimport * from .gen cimport Gen, objtogen @@ -390,19 +392,13 @@ cdef void python_putchar(char c): cdef char s[2] s[0] = c s[1] = 0 - IF PY_MAJOR_VERSION == 2: - sys.stdout.write(s) - ELSE: - sys.stdout.write(bytes(s).decode('ascii')) + sys.stdout.write(to_string( s)) # Let PARI think the last character was a newline, # so it doesn't print one when an error occurs. pari_set_last_newline(1) cdef void python_puts(const char* s): - IF PY_MAJOR_VERSION == 2: - sys.stdout.write(s) - ELSE: - sys.stdout.write(bytes(s).decode('ascii')) + sys.stdout.write(to_string( s)) pari_set_last_newline(1) cdef void python_flush(): @@ -794,6 +790,10 @@ cdef class Pari(Pari_auto): >>> a = pari('1/2'); a, a.type() (1/2, 't_FRAC') + >>> s = pari(u'"éàèç"') + >>> s.type() + 't_STR' + See :func:`pari` for more examples. """ return objtogen(s) @@ -1379,14 +1379,7 @@ cdef long get_var(v) except -2: return varno if v == -1: return -1 - cdef bytes s - IF PY_MAJOR_VERSION == 2: - s = bytes(v) - ELSE: - if isinstance(v, str): - s = (v).encode('ascii') - else: - s = bytes(v) + cdef bytes s = to_bytes(v) sig_on() varno = fetch_user_var(s) sig_off() diff --git a/cypari2/string_utils.pyx b/cypari2/string_utils.pyx new file mode 100644 index 00000000..867028dc --- /dev/null +++ b/cypari2/string_utils.pyx @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +r""" +Conversion functions for bytes/unicode +""" + +import sys +encoding = sys.getfilesystemencoding() + +cpdef bytes to_bytes(s): + """ + Examples: + + >>> from cypari2.string_utils import to_bytes + >>> s1 = to_bytes(b'hello') + >>> s2 = to_bytes('hello') + >>> s3 = to_bytes(u'hello') + >>> type(s1) == type(s2) == type(s3) == bytes + True + >>> s1 == s2 == s3 == b'hello' + True + """ + if isinstance(s, bytes): + return s + elif isinstance(s, unicode): + return ( s).encode(encoding) + else: + raise TypeError + +cpdef str to_string(s): + r""" + Examples: + + >>> from cypari2.string_utils import to_string + >>> s1 = to_string(b'hello') + >>> s2 = to_string('hello') + >>> s3 = to_string(u'hello') + >>> type(s1) == type(s2) == type(s3) == str + True + >>> s1 == s2 == s3 == 'hello' + True + """ + if isinstance(s, bytes): + IF PY_MAJOR_VERSION == 2: + return s + ELSE: + return ( s).decode(encoding) + elif isinstance(s, unicode): + IF PY_MAJOR_VERSION == 2: + return ( s).encode(encoding) + ELSE: + return s + else: + raise TypeError From 308eb2b7ca47ce25d6f4112d6cb4f47ecb9017d0 Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Tue, 6 Jun 2017 23:36:30 +0300 Subject: [PATCH 08/22] better rundoctests --- tests/rundoctest.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/rundoctest.py b/tests/rundoctest.py index 25e110bc..18a6accd 100644 --- a/tests/rundoctest.py +++ b/tests/rundoctest.py @@ -1,13 +1,19 @@ #!/usr/bin/env python +import os import sys import cypari2 import doctest +path = os.path.dirname(__file__) +if path: + os.chdir(path) + failed = 0 attempted = 0 for mod in [cypari2.closure, cypari2.convert, cypari2.gen, - cypari2.handle_error, cypari2.pari_instance, cypari2.stack]: + cypari2.handle_error, cypari2.pari_instance, cypari2.stack, + cypari2.string_utils]: print("="*80) print("Testing {}".format(mod.__name__)) From fd70584a1411340e68864b88325051cdb2f6dd24 Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Tue, 6 Jun 2017 23:54:09 +0300 Subject: [PATCH 09/22] string_utils.pxd --- cypari2/string_utils.pxd | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 cypari2/string_utils.pxd diff --git a/cypari2/string_utils.pxd b/cypari2/string_utils.pxd new file mode 100644 index 00000000..4e6f3a6e --- /dev/null +++ b/cypari2/string_utils.pxd @@ -0,0 +1,2 @@ +cpdef bytes to_bytes(s) +cpdef str to_string(s) From aa2a078c443df1d65bf0ee3a1137cd5d6ca9c682 Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Wed, 7 Jun 2017 00:47:45 +0300 Subject: [PATCH 10/22] fix autogen --- autogen/args.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/autogen/args.py b/autogen/args.py index 3db11ab1..2a6aff59 100644 --- a/autogen/args.py +++ b/autogen/args.py @@ -221,7 +221,8 @@ def convert_code(self): s += " if {name} is None:\n" s += " {tmp} = {default}\n" s += " else:\n" - s += " {tmp} = to_bytes({name})\n" + s += " {name} = to_bytes({name})\n" + s += " {tmp} = name\n" return s.format(name=self.name, tmp=self.tmpname, default=self.default) def call_code(self): return self.tmpname From 576e3c5e6c946e2637a64f9c79eb1a4986da2e8d Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Wed, 7 Jun 2017 09:07:50 +0300 Subject: [PATCH 11/22] fix args.py --- autogen/args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autogen/args.py b/autogen/args.py index 2a6aff59..a4d73e5f 100644 --- a/autogen/args.py +++ b/autogen/args.py @@ -222,7 +222,7 @@ def convert_code(self): s += " {tmp} = {default}\n" s += " else:\n" s += " {name} = to_bytes({name})\n" - s += " {tmp} = name\n" + s += " {tmp} = {name}\n" return s.format(name=self.name, tmp=self.tmpname, default=self.default) def call_code(self): return self.tmpname From f65f7b95ebab986a194bf527a9d8023f146c2fd0 Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Fri, 16 Jun 2017 15:34:37 +0200 Subject: [PATCH 12/22] better and simpler string_utils --- cypari2/string_utils.pxd | 3 ++- cypari2/string_utils.pyx | 48 +++++++++++++++++++++++++++++----------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/cypari2/string_utils.pxd b/cypari2/string_utils.pxd index 4e6f3a6e..ca14ca84 100644 --- a/cypari2/string_utils.pxd +++ b/cypari2/string_utils.pxd @@ -1,2 +1,3 @@ cpdef bytes to_bytes(s) -cpdef str to_string(s) +cpdef unicode to_unicode(s) +cpdef to_string(s) diff --git a/cypari2/string_utils.pyx b/cypari2/string_utils.pyx index 867028dc..895b3b4b 100644 --- a/cypari2/string_utils.pyx +++ b/cypari2/string_utils.pyx @@ -8,6 +8,8 @@ encoding = sys.getfilesystemencoding() cpdef bytes to_bytes(s): """ + Converts bytes and unicode ``s`` to bytes. + Examples: >>> from cypari2.string_utils import to_bytes @@ -26,8 +28,36 @@ cpdef bytes to_bytes(s): else: raise TypeError -cpdef str to_string(s): +cpdef unicode to_unicode(s): + r""" + Converts bytes and unicode ``s`` to unicode. + + Examples: + + >>> from cypari2.string_utils import to_unicode + >>> s1 = to_unicode(b'hello') + >>> s2 = to_unicode('hello') + >>> s3 = to_unicode(u'hello') + >>> import sys + >>> u_type = (unicode if sys.version_info.major == 2 else str) + >>> type(s1) == type(s2) == type(s3) == u_type + True + >>> s1 == s2 == s3 == u'hello' + True + """ + if isinstance(s, bytes): + return ( s).decode(encoding) + elif isinstance(s, unicode): + return s + else: + raise TypeError + +cpdef to_string(s): r""" + Converts a bytes and unicode ``s`` to a string. + + String means bytes in Python2 and unicode in Python3 + Examples: >>> from cypari2.string_utils import to_string @@ -39,15 +69,7 @@ cpdef str to_string(s): >>> s1 == s2 == s3 == 'hello' True """ - if isinstance(s, bytes): - IF PY_MAJOR_VERSION == 2: - return s - ELSE: - return ( s).decode(encoding) - elif isinstance(s, unicode): - IF PY_MAJOR_VERSION == 2: - return ( s).encode(encoding) - ELSE: - return s - else: - raise TypeError + IF PY_MAJOR_VERSION == 2: + return to_bytes(s) + ELSE: + return to_unicode(s) From 386e9563811ab24fd7cc668163d22df278e1da77 Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Fri, 16 Jun 2017 15:58:17 +0200 Subject: [PATCH 13/22] get rid of compile_time_env --- cypari2/convert.pyx | 9 +- cypari2/gen.pyx | 347 ++++++++++++++++++--------------------- cypari2/string_utils.pyx | 7 +- setup.py | 2 - 4 files changed, 169 insertions(+), 196 deletions(-) diff --git a/cypari2/convert.pyx b/cypari2/convert.pyx index bca3fbc1..0d4c667b 100644 --- a/cypari2/convert.pyx +++ b/cypari2/convert.pyx @@ -54,6 +54,9 @@ from .paridecl cimport * from .stack cimport new_gen from .string_utils cimport to_string +import sys +cdef int PY_MAJOR_VERSION = sys.version_info.major + cdef extern from *: Py_ssize_t* Py_SIZE_PTR "&Py_SIZE"(object) @@ -91,17 +94,19 @@ cpdef integer_to_gen(x): ... if pari(long(x)) != pari(x): ... print(x) """ - IF PY_MAJOR_VERSION == 2: + if PY_MAJOR_VERSION == 2: if isinstance(x, int): sig_on() return new_gen(stoi(PyInt_AS_LONG(x))) elif isinstance(x, long): sig_on() return new_gen(PyLong_AsGEN(x)) - ELSE: + elif PY_MAJOR_VERSION == 3: if isinstance(x, int): sig_on() return new_gen(PyLong_AsGEN(x)) + else: + raise RuntimeError raise TypeError("integer_to_gen() needs an int or long argument, not {}".format(type(x).__name__)) diff --git a/cypari2/gen.pyx b/cypari2/gen.pyx index 0419f0ec..02c070e3 100644 --- a/cypari2/gen.pyx +++ b/cypari2/gen.pyx @@ -1489,170 +1489,143 @@ cdef class Gen(Gen_auto): sig_off() return r - IF PY_MAJOR_VERSION == 2: - def __cmp__(self, Gen other): - """ - Compare ``left`` and ``right``. - - This uses PARI's ``cmp_universal()`` routine, which defines - a total ordering on the set of all PARI objects (up to the - indistinguishability relation given by ``gidentical()``). - - .. WARNING:: - - This comparison is only mathematically meaningful when - comparing 2 integers. In particular, when comparing - rationals or reals, this does not correspond to the natural - ordering. - - Examples: - - >>> from cypari2 import Pari - >>> pari = Pari() - - >>> cmp(pari(5), 5) - 0 - >>> cmp(pari(5), 10) - -1 - >>> cmp(pari(2.5), None) - 1 - >>> cmp(pari(3), pari(3)) - 0 - >>> cmp(pari('x^2 + 1'), pari('I-1')) - 1 - >>> I = pari('I') - >>> cmp(I, I) - 0 - - Beware when comparing rationals or reals: - - >>> cmp(pari('2/3'), pari('2/5')) - -1 - >>> two = pari('2.000000000000000000000000') - >>> cmp(two, pari(1.0)) - 1 - >>> cmp(two, pari(2.0)) - 1 - >>> cmp(two, pari(3.0)) - 1 - - Since :trac:`17026`, different elements with the same string - representation can be distinguished by ``cmp()``: - - >>> a = pari(0); a - 0 - >>> b = pari("0*ffgen(ffinit(29, 10))"); b - 0 - >>> cmp(a, b) - -1 - - >>> x = pari("x"); x - x - >>> y = pari("ffgen(ffinit(3, 5))"); y - x - >>> cmp(x, y) - 1 - """ - sig_on() - cdef int r = cmp_universal(self.g, other.g) - sig_off() - return r + def __cmp__(self, Gen other): + """ + Compare ``left`` and ``right``. + + This uses PARI's ``cmp_universal()`` routine, which defines + a total ordering on the set of all PARI objects (up to the + indistinguishability relation given by ``gidentical()``). + + .. WARNING:: + + This comparison is only mathematically meaningful when + comparing 2 integers. In particular, when comparing + rationals or reals, this does not correspond to the natural + ordering. + + Examples: + + >>> from cypari2 import Pari + >>> pari = Pari() + >>> import sys + + >>> if sys.version_info.major == 2: + ... assert cmp(pari(5), 5) == 0 + ... assert cmp(pari(5), 10) == -1 + ... assert cmp(pari(2.5), None) == 1 + ... assert cmp(pari(3), pari(3)) == 0 + ... assert cmp(pari('x^2 + 1'), pari('I-1')) == 1 + ... I = pari('I') + ... assert cmp(I, I) == 0 + ... assert cmp(pari('2/3'), pari('2/5')) == -1 + ... two = pari('2.000000000000000000000000') + ... assert cmp(two, pari(1.0)) == 1 + ... assert cmp(two, pari(2.0)) == 1 + ... assert cmp(two, pari(3.0)) == 1 + ... a = pari(0) + ... b = pari("0*ffgen(ffinit(29, 10))") + ... assert cmp(a, b) == -1 + ... x = pari("x") + ... y = pari("ffgen(ffinit(3, 5))") + ... assert cmp(x, y) == 1 + """ + sig_on() + cdef int r = cmp_universal(self.g, other.g) + sig_off() + return r def __copy__(self): sig_on() return new_gen(self.g) - # NOTE: In Python3, oct(xxx) calls __index__ - IF PY_MAJOR_VERSION == 2: - def __oct__(self): - """ - Return the octal digits of self in lower case. - """ - cdef GEN x - cdef long lx - cdef long *xp - cdef long w - cdef char *s - cdef char *sp - cdef char *octdigits = "01234567" - cdef int i, j - cdef int size - x = self.g - if typ(x) != t_INT: - raise TypeError("gen must be of PARI type t_INT") - if not signe(x): - return "0" - lx = lgefint(x) - 2 # number of words - size = lx * 4 * sizeof(long) - s = sig_malloc(size+3) # 1 char for sign, 1 char for 0, 1 char for '\0' - sp = s + size + 3 - sp[0] = 0 - xp = int_LSW(x) - for i from 0 <= i < lx: - w = xp[0] - for j in range(4*sizeof(long)): - sp -= 1 - sp[0] = octdigits[w & 7] - w >>= 3 - xp = int_nextW(xp) - # remove leading zeros! - while sp[0] == c'0': - sp += 1 - sp -= 1 - sp[0] = c'0' - if signe(x) < 0: + def __oct__(self): + """ + Return the octal digits of self in lower case. + """ + cdef GEN x + cdef long lx + cdef long *xp + cdef long w + cdef char *s + cdef char *sp + cdef char *octdigits = "01234567" + cdef int i, j + cdef int size + x = self.g + if typ(x) != t_INT: + raise TypeError("gen must be of PARI type t_INT") + if not signe(x): + return "0" + lx = lgefint(x) - 2 # number of words + size = lx * 4 * sizeof(long) + s = sig_malloc(size+3) # 1 char for sign, 1 char for 0, 1 char for '\0' + sp = s + size + 3 + sp[0] = 0 + xp = int_LSW(x) + for i from 0 <= i < lx: + w = xp[0] + for j in range(4*sizeof(long)): sp -= 1 - sp[0] = c'-' - k = sp - sig_free(s) - return k - - # NOTE: In Python3, hex(xxx) calls __index__ - IF PY_MAJOR_VERSION == 2: - def __hex__(self): - """ - Return the hexadecimal digits of self in lower case. - """ - cdef GEN x - cdef long lx - cdef long *xp - cdef long w - cdef char *s - cdef char *sp - cdef char *hexdigits = "0123456789abcdef" - cdef int i, j - cdef int size - x = self.g - if typ(x) != t_INT: - raise TypeError("gen must be of PARI type t_INT") - if not signe(x): - return "0x0" - lx = lgefint(x) - 2 # number of words - size = lx*2*sizeof(long) - s = sig_malloc(size+4) # 1 char for sign, 2 chars for 0x, 1 char for '\0' - sp = s + size + 4 - sp[0] = 0 - xp = int_LSW(x) - for i from 0 <= i < lx: - w = xp[0] - for j in range(2*sizeof(long)): - sp -= 1 - sp[0] = hexdigits[w & 15] - w >>= 4 - xp = int_nextW(xp) - # remove leading zeros! - while sp[0] == c'0': - sp = sp + 1 - sp -= 1 - sp[0] = 'x' + sp[0] = octdigits[w & 7] + w >>= 3 + xp = int_nextW(xp) + # remove leading zeros! + while sp[0] == c'0': + sp += 1 + sp -= 1 + sp[0] = c'0' + if signe(x) < 0: sp -= 1 - sp[0] = '0' - if signe(x) < 0: + sp[0] = c'-' + k = sp + sig_free(s) + return k + + def __hex__(self): + """ + Return the hexadecimal digits of self in lower case. + """ + cdef GEN x + cdef long lx + cdef long *xp + cdef long w + cdef char *s + cdef char *sp + cdef char *hexdigits = "0123456789abcdef" + cdef int i, j + cdef int size + x = self.g + if typ(x) != t_INT: + raise TypeError("gen must be of PARI type t_INT") + if not signe(x): + return "0x0" + lx = lgefint(x) - 2 # number of words + size = lx*2*sizeof(long) + s = sig_malloc(size+4) # 1 char for sign, 2 chars for 0x, 1 char for '\0' + sp = s + size + 4 + sp[0] = 0 + xp = int_LSW(x) + for i from 0 <= i < lx: + w = xp[0] + for j in range(2*sizeof(long)): sp -= 1 - sp[0] = c'-' - k = sp - sig_free(s) - return k + sp[0] = hexdigits[w & 15] + w >>= 4 + xp = int_nextW(xp) + # remove leading zeros! + while sp[0] == c'0': + sp = sp + 1 + sp -= 1 + sp[0] = 'x' + sp -= 1 + sp[0] = '0' + if signe(x) < 0: + sp -= 1 + sp[0] = c'-' + k = sp + sig_free(s) + return k def __int__(self): """ @@ -1820,40 +1793,34 @@ cdef class Gen(Gen_auto): from sage.libs.pari.convert_sage import gen_to_sage return gen_to_sage(self, locals) - IF PY_MAJOR_VERSION == 2: - def __long__(self): - """ - Convert ``self`` to a Python ``long``. - - Examples: - - >>> from cypari2 import Pari - >>> pari = Pari() - - >>> long(pari(0)) - 0L - >>> long(pari(10)) - 10L - >>> long(pari(-10)) - -10L - >>> long(pari(123456789012345678901234567890)) - 123456789012345678901234567890L - >>> long(pari(-123456789012345678901234567890)) - -123456789012345678901234567890L - >>> long(pari(2**31-1)) - 2147483647L - >>> long(pari(-2**31)) - -2147483648L - >>> long(pari("Pol(10)")) - 10L - >>> long(pari("Mod(2, 7)")) - 2L - """ - x = gen_to_integer(self) - if isinstance(x, long): - return x - else: - return long(x) + def __long__(self): + """ + Convert ``self`` to a Python ``long``. + + Examples: + + >>> from cypari2 import Pari + >>> pari = Pari() + >>> import sys + + >>> if sys.version_info.major == 3: + ... long = int + >>> assert isinstance(long(pari(0)), long) + >>> assert long(pari(0)) == 0 + >>> assert long(pari(10)) == 10 + >>> assert long(pari(-10)) == -10 + >>> assert long(pari(123456789012345678901234567890)) == 123456789012345678901234567890 + >>> assert long(pari(-123456789012345678901234567890)) == -123456789012345678901234567890 + >>> assert long(pari(2**31-1)) == 2147483647 + >>> assert long(pari(-2**31)) == -2147483648 + >>> assert long(pari("Pol(10)")) == 10 + >>> assert long(pari("Mod(2, 7)")) == 2 + """ + x = gen_to_integer(self) + if isinstance(x, long): + return x + else: + return long(x) def __float__(self): """ diff --git a/cypari2/string_utils.pyx b/cypari2/string_utils.pyx index 895b3b4b..ca823766 100644 --- a/cypari2/string_utils.pyx +++ b/cypari2/string_utils.pyx @@ -5,6 +5,7 @@ Conversion functions for bytes/unicode import sys encoding = sys.getfilesystemencoding() +cdef int PY_MAJOR_VERSION = sys.version_info.major cpdef bytes to_bytes(s): """ @@ -69,7 +70,9 @@ cpdef to_string(s): >>> s1 == s2 == s3 == 'hello' True """ - IF PY_MAJOR_VERSION == 2: + if PY_MAJOR_VERSION == 2: return to_bytes(s) - ELSE: + elif PY_MAJOR_VERSION == 3: return to_unicode(s) + else: + raise RuntimeError diff --git a/setup.py b/setup.py index f0436348..8ea2d035 100755 --- a/setup.py +++ b/setup.py @@ -29,11 +29,9 @@ def finalize_options(self): rebuild() self.directives = dict(binding=True) - self.compile_time_env = dict(PY_MAJOR_VERSION=sys.version_info.major) self.distribution.ext_modules[:] = cythonize( self.distribution.ext_modules, - compile_time_env=self.compile_time_env, compiler_directives=self.directives, include_path=sys.path) From f497c2f8a18426650caeac12646c66444dad7bec Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Fri, 16 Jun 2017 21:45:23 +0200 Subject: [PATCH 14/22] some more improvements towards Py2/Py3 compat --- cypari2/convert.pyx | 20 +++++--------------- cypari2/string_utils.pyx | 10 ++++------ 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/cypari2/convert.pyx b/cypari2/convert.pyx index 0d4c667b..1730a1d9 100644 --- a/cypari2/convert.pyx +++ b/cypari2/convert.pyx @@ -54,9 +54,6 @@ from .paridecl cimport * from .stack cimport new_gen from .string_utils cimport to_string -import sys -cdef int PY_MAJOR_VERSION = sys.version_info.major - cdef extern from *: Py_ssize_t* Py_SIZE_PTR "&Py_SIZE"(object) @@ -94,21 +91,14 @@ cpdef integer_to_gen(x): ... if pari(long(x)) != pari(x): ... print(x) """ - if PY_MAJOR_VERSION == 2: - if isinstance(x, int): + if isinstance(x, long): + sig_on() + return new_gen(PyLong_AsGEN(x)) + elif isinstance(x, int): sig_on() return new_gen(stoi(PyInt_AS_LONG(x))) - elif isinstance(x, long): - sig_on() - return new_gen(PyLong_AsGEN(x)) - elif PY_MAJOR_VERSION == 3: - if isinstance(x, int): - sig_on() - return new_gen(PyLong_AsGEN(x)) else: - raise RuntimeError - - raise TypeError("integer_to_gen() needs an int or long argument, not {}".format(type(x).__name__)) + raise TypeError("integer_to_gen() needs an int or long argument, not {}".format(type(x).__name__)) cpdef gen_to_integer(Gen x): """ diff --git a/cypari2/string_utils.pyx b/cypari2/string_utils.pyx index ca823766..9081f00d 100644 --- a/cypari2/string_utils.pyx +++ b/cypari2/string_utils.pyx @@ -23,7 +23,7 @@ cpdef bytes to_bytes(s): True """ if isinstance(s, bytes): - return s + return s elif isinstance(s, unicode): return ( s).encode(encoding) else: @@ -49,7 +49,7 @@ cpdef unicode to_unicode(s): if isinstance(s, bytes): return ( s).decode(encoding) elif isinstance(s, unicode): - return s + return s else: raise TypeError @@ -70,9 +70,7 @@ cpdef to_string(s): >>> s1 == s2 == s3 == 'hello' True """ - if PY_MAJOR_VERSION == 2: + if PY_MAJOR_VERSION <= 2: return to_bytes(s) - elif PY_MAJOR_VERSION == 3: - return to_unicode(s) else: - raise RuntimeError + return to_unicode(s) From 5b664609adce15505ec5643112133910767be365 Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Mon, 10 Jul 2017 09:11:00 +0200 Subject: [PATCH 15/22] tests long/int in Python2 --- cypari2/convert.pyx | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/cypari2/convert.pyx b/cypari2/convert.pyx index 1730a1d9..186cc18d 100644 --- a/cypari2/convert.pyx +++ b/cypari2/convert.pyx @@ -84,19 +84,20 @@ cpdef integer_to_gen(x): Tests: >>> import sys - >>> if sys.version_info.major == 2: - ... assert integer_to_gen(long(12345)) == 12345 - ... for i in range(10000): - ... x = 3**i - ... if pari(long(x)) != pari(x): - ... print(x) + >>> if sys.version_info.major == 3: + ... long = int + >>> assert integer_to_gen(long(12345)) == 12345 + >>> for i in range(10000): + ... x = 3**i + ... if pari(long(x)) != pari(x) or pari(int(x)) != pari(x): + ... print(x) """ if isinstance(x, long): sig_on() return new_gen(PyLong_AsGEN(x)) elif isinstance(x, int): - sig_on() - return new_gen(stoi(PyInt_AS_LONG(x))) + sig_on() + return new_gen(stoi(PyInt_AS_LONG(x))) else: raise TypeError("integer_to_gen() needs an int or long argument, not {}".format(type(x).__name__)) @@ -155,12 +156,12 @@ cpdef gen_to_integer(Gen x): >>> gen_to_integer(pari("1 - 2^64")) == -18446744073709551615 True >>> import sys - >>> if sys.version_info.major == 2: - ... for i in range(10000): - ... x = 3**i - ... if long(pari(x)) != long(x): - ... print(x) - + >>> if sys.version_info.major == 3: + ... long = int + >>> for i in range(10000): + ... x = 3**i + ... if long(pari(x)) != long(x) or int(pari(x)) != x: + ... print(x) Check some corner cases: From 3040e651256187b17db32d8fa85b254ef1dd7470 Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Mon, 10 Jul 2017 09:12:42 +0200 Subject: [PATCH 16/22] remove list(map(...)) --- cypari2/convert.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cypari2/convert.pyx b/cypari2/convert.pyx index 186cc18d..f0e28997 100644 --- a/cypari2/convert.pyx +++ b/cypari2/convert.pyx @@ -536,21 +536,21 @@ cpdef gen_to_python(Gen z): [1, 2, 3] >>> type(a1) <... 'list'> - >>> list(map(type, a1)) + >>> [type(x) for x in a1] [<... 'int'>, <... 'int'>, <... 'int'>] >>> a2 = gen_to_python(z2); a2 [1, 3.4, [-5, 2], inf] >>> type(a2) <... 'list'> - >>> list(map(type, a2)) + >>> [type(x) for x in a2] [<... 'int'>, <... 'float'>, <... 'list'>, <... 'float'>] >>> a3 = gen_to_python(z3); a3 [1, 5.2] >>> type(a3) <... 'list'> - >>> list(map(type, a3)) + >>> [type(x) for x in a3] [<... 'int'>, <... 'float'>] Converting matrices: From b5e537569f4bce7ac2e8ef2533bb163d05dd3106 Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Mon, 10 Jul 2017 09:24:17 +0200 Subject: [PATCH 17/22] dropping c_string_encoding=default --- cypari2/gen.pyx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/cypari2/gen.pyx b/cypari2/gen.pyx index 02c070e3..a8eac12e 100644 --- a/cypari2/gen.pyx +++ b/cypari2/gen.pyx @@ -1,6 +1,3 @@ -# Use sys.getdefaultencoding() to convert Unicode strings to -# -# cython: c_string_encoding=default """ The Gen class wrapping PARI's GEN type ************************************** @@ -184,7 +181,7 @@ cdef class Gen(Gen_auto): # Use __repr__ except for strings if typ(self.g) == t_STR: # CHANGED - return GSTR(self.g) + return to_string( GSTR(self.g)) return repr(self) def __hash__(self): @@ -323,7 +320,7 @@ cdef class Gen(Gen_auto): elif t == t_STR: # Special case: convert to str # CHANGED - return iter( GSTR(self.g)) + return iter(to_string( GSTR(self.g))) else: v = self.Vec() @@ -653,7 +650,8 @@ cdef class Gen(Gen_auto): ... PariError: not a function in function call """ - t = "_." + attr + attr = to_bytes(attr) + t = b"_." + attr sig_on() return new_gen(closure_callgen1(strtofunction(t), self.g)) From 92f558b92886c7f8d6b88fef5da6b4951d22ceb6 Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Mon, 10 Jul 2017 09:28:10 +0200 Subject: [PATCH 18/22] remove in to_string( X) --- cypari2/convert.pyx | 2 +- cypari2/gen.pyx | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cypari2/convert.pyx b/cypari2/convert.pyx index f0e28997..7a10b080 100644 --- a/cypari2/convert.pyx +++ b/cypari2/convert.pyx @@ -618,6 +618,6 @@ cpdef gen_to_python(Gen z): else: return -INFINITY elif t == t_STR: - return to_string( GSTR(g)) + return to_string(GSTR(g)) else: raise NotImplementedError("conversion not implemented for {}".format(z.type())) diff --git a/cypari2/gen.pyx b/cypari2/gen.pyx index a8eac12e..fa0a117a 100644 --- a/cypari2/gen.pyx +++ b/cypari2/gen.pyx @@ -180,8 +180,7 @@ cdef class Gen(Gen_auto): """ # Use __repr__ except for strings if typ(self.g) == t_STR: - # CHANGED - return to_string( GSTR(self.g)) + return to_string(GSTR(self.g)) return repr(self) def __hash__(self): @@ -320,7 +319,7 @@ cdef class Gen(Gen_auto): elif t == t_STR: # Special case: convert to str # CHANGED - return iter(to_string( GSTR(self.g))) + return iter(to_string(GSTR(self.g))) else: v = self.Vec() From fa24efbedd7d50ef1722fa5bcc9d96d6d742e162 Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Mon, 10 Jul 2017 09:35:38 +0200 Subject: [PATCH 19/22] remove an explicit conversion to list --- cypari2/gen.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cypari2/gen.pyx b/cypari2/gen.pyx index fa0a117a..18fea346 100644 --- a/cypari2/gen.pyx +++ b/cypari2/gen.pyx @@ -4193,14 +4193,14 @@ cdef class Gen(Gen_auto): return new_gen(gsubst(self.g, varn(self.g), t0.g)) # Call substvec() using **kwds - vstr = list(kwds.keys()) # Variables as Python strings - t0 = objtogen(list(kwds.values())) # Replacements + vstr = iter(kwds.keys()) # Variables as Python strings + t0 = objtogen(kwds.values()) # Replacements sig_on() cdef GEN v = cgetg(nkwds+1, t_VEC) # Variables as PARI polynomials cdef long i for i in range(nkwds): - set_gel(v, i+1, pol_x(get_var(vstr[i]))) + set_gel(v, i+1, pol_x(get_var(next(vstr)))) return new_gen(gsubstvec(self.g, v, t0.g)) def __call__(self, *args, **kwds): From b4410fce786917b288b823c25129bb28323b5298 Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Mon, 10 Jul 2017 09:55:20 +0200 Subject: [PATCH 20/22] avoid conversion bytes -> string -> bytes --- cypari2/pari_instance.pyx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cypari2/pari_instance.pyx b/cypari2/pari_instance.pyx index f43d218e..8653e878 100644 --- a/cypari2/pari_instance.pyx +++ b/cypari2/pari_instance.pyx @@ -393,13 +393,21 @@ cdef void python_putchar(char c): cdef char s[2] s[0] = c s[1] = 0 - sys.stdout.write(to_string( s)) + try: + # avoid string conversion if possible + sys.stdout.buffer.write(s) + except AttributeError: + sys.stdout.write(to_string(s)) # Let PARI think the last character was a newline, # so it doesn't print one when an error occurs. pari_set_last_newline(1) cdef void python_puts(const char* s): - sys.stdout.write(to_string( s)) + try: + # avoid string conversion if possible + sys.stdout.buffer.write(s) + except AttributeError: + sys.stdout.write(to_string(s)) pari_set_last_newline(1) cdef void python_flush(): From d9efdf88a3ff8555fd10f59394b12076dfc6a6ee Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Thu, 13 Jul 2017 13:46:20 +0200 Subject: [PATCH 21/22] compile time PY_MAJOR_VERSION + inlined to_string --- cypari2/string_utils.pxd | 26 +++++++++++++++++++++++++- cypari2/string_utils.pyx | 24 +----------------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/cypari2/string_utils.pxd b/cypari2/string_utils.pxd index ca14ca84..8600c706 100644 --- a/cypari2/string_utils.pxd +++ b/cypari2/string_utils.pxd @@ -1,3 +1,27 @@ +cdef extern from *: + int PY_MAJOR_VERSION + cpdef bytes to_bytes(s) cpdef unicode to_unicode(s) -cpdef to_string(s) + +cpdef inline to_string(s): + r""" + Converts a bytes and unicode ``s`` to a string. + + String means bytes in Python2 and unicode in Python3 + + Examples: + + >>> from cypari2.string_utils import to_string + >>> s1 = to_string(b'hello') + >>> s2 = to_string('hello') + >>> s3 = to_string(u'hello') + >>> type(s1) == type(s2) == type(s3) == str + True + >>> s1 == s2 == s3 == 'hello' + True + """ + if PY_MAJOR_VERSION <= 2: + return to_bytes(s) + else: + return to_unicode(s) diff --git a/cypari2/string_utils.pyx b/cypari2/string_utils.pyx index 9081f00d..8e422877 100644 --- a/cypari2/string_utils.pyx +++ b/cypari2/string_utils.pyx @@ -5,7 +5,6 @@ Conversion functions for bytes/unicode import sys encoding = sys.getfilesystemencoding() -cdef int PY_MAJOR_VERSION = sys.version_info.major cpdef bytes to_bytes(s): """ @@ -40,7 +39,7 @@ cpdef unicode to_unicode(s): >>> s2 = to_unicode('hello') >>> s3 = to_unicode(u'hello') >>> import sys - >>> u_type = (unicode if sys.version_info.major == 2 else str) + >>> u_type = (unicode if sys.version_info.major <= 2 else str) >>> type(s1) == type(s2) == type(s3) == u_type True >>> s1 == s2 == s3 == u'hello' @@ -53,24 +52,3 @@ cpdef unicode to_unicode(s): else: raise TypeError -cpdef to_string(s): - r""" - Converts a bytes and unicode ``s`` to a string. - - String means bytes in Python2 and unicode in Python3 - - Examples: - - >>> from cypari2.string_utils import to_string - >>> s1 = to_string(b'hello') - >>> s2 = to_string('hello') - >>> s3 = to_string(u'hello') - >>> type(s1) == type(s2) == type(s3) == str - True - >>> s1 == s2 == s3 == 'hello' - True - """ - if PY_MAJOR_VERSION <= 2: - return to_bytes(s) - else: - return to_unicode(s) From a286c14f1ff87d81ba64d9c17d280e8bbe279ee7 Mon Sep 17 00:00:00 2001 From: Vincent Delecroix Date: Thu, 13 Jul 2017 15:03:12 +0200 Subject: [PATCH 22/22] .keys() -> .iterkeys() --- cypari2/gen.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypari2/gen.pyx b/cypari2/gen.pyx index 18fea346..5c9a2997 100644 --- a/cypari2/gen.pyx +++ b/cypari2/gen.pyx @@ -4193,7 +4193,7 @@ cdef class Gen(Gen_auto): return new_gen(gsubst(self.g, varn(self.g), t0.g)) # Call substvec() using **kwds - vstr = iter(kwds.keys()) # Variables as Python strings + vstr = iter(kwds.iterkeys()) # Variables as Python strings t0 = objtogen(kwds.values()) # Replacements sig_on()