diff --git a/autogen/args.py b/autogen/args.py index b7b4939e..a4d73e5f 100644 --- a/autogen/args.py +++ b/autogen/args.py @@ -214,15 +214,15 @@ 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" + 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" - s += " {name} = bytes({name})\n" - s += " {tmp} = {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 diff --git a/cypari2/closure.pyx b/cypari2/closure.pyx index bf5dcabf..861c4da2 100644 --- a/cypari2/closure.pyx +++ b/cypari2/closure.pyx @@ -156,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 7bfab22e..7a10b080 100644 --- a/cypari2/convert.pyx +++ b/cypari2/convert.pyx @@ -52,11 +52,11 @@ 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) - #################################### # Integers #################################### @@ -74,28 +74,32 @@ 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: + >>> import sys + >>> 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): + ... if pari(long(x)) != pari(x) or pari(int(x)) != pari(x): ... print(x) """ - if isinstance(x, int): - sig_on() - return new_gen(stoi(PyInt_AS_LONG(x))) - elif isinstance(x, long): + if isinstance(x, long): sig_on() return new_gen(PyLong_AsGEN(x)) - raise TypeError("integer_to_gen() needs an int or long argument, not {}".format(type(x).__name__)) + elif isinstance(x, int): + 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__)) cpdef gen_to_integer(Gen x): """ @@ -115,9 +119,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")) @@ -130,11 +133,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 @@ -145,14 +151,17 @@ cpdef gen_to_integer(Gen x): Tests: + >>> 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 == 3: + ... long = int >>> for i in range(10000): ... x = 3**i - ... if long(pari(x)) != long(x): + ... if long(pari(x)) != long(x) or int(pari(x)) != x: ... print(x) - >>> gen_to_integer(pari("1.0 - 2^64")) - -18446744073709551615L - >>> gen_to_integer(pari("1 - 2^64")) - -18446744073709551615L Check some corner cases: @@ -212,9 +221,10 @@ cdef GEN gtoi(GEN g0) except NULL: sig_error() sig_off() except RuntimeError: - 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)))) + raise TypeError(s) return g @@ -460,9 +470,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: @@ -525,21 +536,21 @@ cpdef gen_to_python(Gen z): [1, 2, 3] >>> type(a1) <... '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'> - >>> 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'> - >>> map(type, a3) + >>> [type(x) for x in a3] [<... 'int'>, <... 'float'>] Converting matrices: @@ -607,6 +618,6 @@ cpdef gen_to_python(Gen z): else: return -INFINITY elif t == t_STR: - return str(z) + 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 c8d2aeb8..5c9a2997 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 ************************************** @@ -38,6 +35,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 """ #***************************************************************************** @@ -47,6 +47,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 @@ -71,6 +72,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) @@ -79,10 +81,8 @@ 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' - @cython.final cdef class Gen(Gen_auto): """ @@ -154,9 +154,9 @@ cdef class Gen(Gen_auto): sig_unblock() sig_off() - s = str(c) + s = bytes(c) pari_free(c) - return s + return to_string(s) def __str__(self): """ @@ -171,16 +171,16 @@ 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) + return to_string(GSTR(self.g)) return repr(self) def __hash__(self): @@ -318,7 +318,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 - return iter(GSTR(self.g)) + # CHANGED + return iter(to_string(GSTR(self.g))) else: v = self.Vec() @@ -343,7 +344,7 @@ cdef class Gen(Gen_auto): >>> type(L) <... 'list'> >>> type(L[0]) - + <... 'cypari2.gen.Gen'> For polynomials, list() returns the list of coefficients: @@ -648,7 +649,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)) @@ -1325,7 +1327,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 @@ -1341,7 +1343,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) @@ -1503,49 +1505,27 @@ cdef class Gen(Gen_auto): >>> from cypari2 import Pari >>> pari = Pari() + >>> import sys - >>> 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 + >>> 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) @@ -1556,27 +1536,52 @@ cdef class Gen(Gen_auto): sig_on() return new_gen(self.g) + 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 + 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 @@ -1584,35 +1589,38 @@ cdef class Gen(Gen_auto): cdef long w cdef char *s cdef char *sp - cdef char *hexdigits - hexdigits = "0123456789abcdef" + 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 "0" - lx = lgefint(x)-2 # number of words + return "0x0" + 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 + 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 from 0 <= j < 2*sizeof(long): - sp = sp-1 + for j in range(2*sizeof(long)): + sp -= 1 sp[0] = hexdigits[w & 15] - w = w>>4 + w >>= 4 xp = int_nextW(xp) # remove leading zeros! while sp[0] == c'0': - sp = sp+1 + sp = sp + 1 + sp -= 1 + sp[0] = 'x' + sp -= 1 + sp[0] = '0' if signe(x) < 0: - sp = sp-1 + sp -= 1 sp[0] = c'-' - k = sp + k = sp sig_free(s) return k @@ -1634,10 +1642,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)) @@ -1677,6 +1685,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") @@ -1727,7 +1743,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] @@ -1782,25 +1798,20 @@ cdef class Gen(Gen_auto): >>> from cypari2 import Pari >>> pari = Pari() + >>> import sys - >>> 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 + >>> 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): @@ -3422,7 +3433,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) @@ -3438,7 +3449,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'>) @@ -4182,14 +4193,14 @@ 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 = iter(kwds.iterkeys()) # 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): @@ -4940,7 +4951,7 @@ cpdef Gen objtogen(s): >>> pari(int(-5)) -5 - >>> pari(long(2**150)) + >>> pari(2**150) 1427247692705959881058285969449495136382746624 >>> import math >>> pari(math.pi) @@ -4961,7 +4972,7 @@ cpdef Gen objtogen(s): >>> pari("dummy = 0; kill(dummy)") >>> type(pari("dummy = 0; kill(dummy)")) - + <... 'NoneType'> Tests: @@ -5008,7 +5019,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 43855e1b..8653e878 100644 --- a/cypari2/pari_instance.pyx +++ b/cypari2/pari_instance.pyx @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Interface to the PARI library ***************************** @@ -51,9 +52,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 ============================================= @@ -232,6 +233,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 @@ -385,22 +387,30 @@ 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) + 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 sage_puts(const char* s): - sys.stdout.write(s) +cdef void python_puts(const char* 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 sage_flush(): +cdef void python_flush(): sys.stdout.flush() include 'auto_instance.pxi' @@ -416,6 +426,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: @@ -435,10 +447,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) @@ -580,16 +592,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 @@ -797,6 +799,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) @@ -1382,7 +1388,7 @@ cdef long get_var(v) except -2: return varno if v == -1: return -1 - cdef bytes s = bytes(v) + cdef bytes s = to_bytes(v) sig_on() varno = fetch_user_var(s) sig_off() diff --git a/cypari2/string_utils.pxd b/cypari2/string_utils.pxd new file mode 100644 index 00000000..8600c706 --- /dev/null +++ b/cypari2/string_utils.pxd @@ -0,0 +1,27 @@ +cdef extern from *: + int PY_MAJOR_VERSION + +cpdef bytes to_bytes(s) +cpdef unicode to_unicode(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 new file mode 100644 index 00000000..8e422877 --- /dev/null +++ b/cypari2/string_utils.pyx @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +r""" +Conversion functions for bytes/unicode +""" + +import sys +encoding = sys.getfilesystemencoding() + +cpdef bytes to_bytes(s): + """ + Converts bytes and unicode ``s`` to bytes. + + 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 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 + diff --git a/tests/rundoctest.py b/tests/rundoctest.py index 909d9395..18a6accd 100644 --- a/tests/rundoctest.py +++ b/tests/rundoctest.py @@ -1,17 +1,23 @@ #!/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__)) - 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