During Sage Days 100, I experienced an issue with Sage's Integers and 
Rational not being compatible with Python's built in Fraction class. This 
made me unable to run my Python code within Sage and so I raised ticket 
28234 <https://trac.sagemath.org/ticket/28234>. For example, in Sage 8.8:

sage: from fractions import Fraction
sage: Fraction(7, 3)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-4a5c214ab004> in <module>()
----> 1 Fraction(Integer(7), Integer(3))


/Applications/SageMath-8.6.app/Contents/Resources/sage/local/lib/python2.7/
fractions.pyc in __new__(cls, numerator, denominator)
    152             isinstance(denominator, Rational)):
    153             numerator, denominator = (
--> 154                 numerator.numerator * denominator.denominator,
    155                 denominator.numerator * numerator.denominator
    156                 )


TypeError: unsupported operand type(s) for *: 'builtin_function_or_method' 
and 'builtin_function_or_method'
sage: Fraction(7)
Fraction(<built-in method numerator of sage.rings.integer.Integer object at 
0x182ab93f0>, <built-in method denominator of sage.rings.integer.Integer 
object at 0x182ab93f0>)
sage: Fraction(7 / 3)
Fraction(<built-in method numerator of sage.rings.rational.Rational object 
at 0x182ab2950>, <built-in method denominator of sage.rings.rational.
Rational object at 0x182ab2950>)

This issue is because, despite the Integer and Rational class being 
registered as Python's numbers.Integral and numbers.Rational classes, they 
do not implement numerator and denominator *properties* as required by the 
Python specification. Instead they implement numerator and denominator 
*methods*. This issue has been spotted a number of times [1] 
<https://trac.sagemath.org/ticket/20861> [2] 
<https://ask.sagemath.org/question/39717>.

During the course of the workshop several solutions were suggested and 
investigated including:

   1. Make Integer and Rational numerator and denominator properties and 
   accept that this would not be backwards compatible within Sage
   2. Make Integer and Rational numerator and denominator properties and 
   add numer and denom methods to classes that have these properties and 
   switch to using these throughout
   3. Make Integer and Rational  numerator and denominator properties and 
   make Integers callable and return themselves
   4. Raise a PEP to change the Python specification to be / include 
   numerator and denominator methods
   5. Do not register Integer and Rational as numbers.Integral and 
   numbers.Rational
   6. Do nothing and so accept that Sage does not perfectly match the 
   Python specification

As part of the workshop, I worked on implementing approach #3 within branch 
28234 
<https://git.sagemath.org/sage.git/diff?id2=a1e1a8f721fa32840968248200327af6a7f35de0&id=4977867e55d76c29b7286e2d542d8342889d8305>.
 
This implementation is now complete and passes all of Sages doctests 
[although its implementation revealed a bug in sagemath-patchbot which I 
subsequently fixed <https://github.com/sagemath/sage-patchbot/pull/139>]. 
This means that now if, for example, x = 5 then both x.numerator and 
x.numerator() return 5 [in the latter case x.numerator returns 5, and then 
5() returns 5]. Hence not only does this make Sage Integers and Rationals 
compatible with Python Fractions:

sage: from fractions import Fraction
sage: Fraction(7, 3)
Fraction(7, 3)
sage: Fraction(7)
Fraction(7, 1)
sage: Fraction(7/3)
Fraction(7, 3)


but this change is morally backwards compatible with existing Sage code.

However, this approach makes Sage Integers callable and so additional work 
was needed to ensure that the control flow within Sage kernel was not 
affected. I used a short Python script to generate a patch (that was 
applied within branch 28234) that:

   1. Replaced all uses of hasattr(x, '__call__') with callable(x)
   2. Replaced all uses of isinstance(x, collections.Callable) with 
   callable(x)
   3. Replaced all uses of callable(x) with callable(x) and not 
   isinstance(x, numbers.Integral)

Therefore if this branch is merged then users may need to make similar 
changes to their code if they ever rely on the fact that Integers were not 
callable.

Now regarding the other approaches:

   - Approach #1 would be backwards incompatibly and would likely affect a 
   substantially larger number of users (anyone who has ever access a 
   numerator). It would also mean that care would be needed as to use 
   x.numerator or x.numerator() depending on the type of x.
   - Approach #2 was attempted but presented difficulties since numer() 
   methods would also need to be added all external objects (e.g. Pari object) 
   that are available within Sage that implement numerator() methods.
   - Approach #4 was also started and discussion about changing the Python 
   specification from PEP 3141 can be found here 
   <https://discuss.python.org/t/pep-3141-numerator-instead-of-numerator/2037>. 
   This is likely to also have a knock-on effect on other systems such as 
   numpy.
   - Approach #5 would also result in serious backwards compatibility 
   issues and would still mean that Sage Integers and Rationals would not be 
   compatible with Python Fractions.


Therefore I would like to ask the Sage development community for their 
opinions on:

   - the different approaches and whether approach #3 is the most suitable
   - whether branch public/28234 is the most suitable implementation of 
   approach #3 and whether it should be merged into Sage
   - whether similar changes should be made to other classes within Sage 
   that implement numerator / denominator methods
   - whether similar changes should be made to the .real() and .imag() 
   methods which should also be properties under the Python specification 
   <https://www.python.org/dev/peps/pep-3141/#numeric-classes>

I would also like to emphasise that although only these six approaches were 
discussed during the workshop, there may well be others. Therefore others 
should be encouraged to alternative approaches so that these can all be 
discussed together.

Regards,
Mark Bell

-- 
You received this message because you are subscribed to the Google Groups 
"sage-devel" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sage-devel+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/sage-devel/595abaa2-2efa-4b64-be52-0afc87210251%40googlegroups.com.

Reply via email to