On 8/22/07, Jeffrey Yasskin <[EMAIL PROTECTED]> wrote: > There are still some open issues here that need answers: > > * Should __pos__ coerce the argument to be an instance of the type > it's defined on?
Yes, I think so. That's what the built-in types do (in case the object is an instance of a subclass). It makes sense because all other operators do this too (unless overridden). > * Add Demo/classes/Rat.py to the stdlib? Yes, but it needs a makeover. At the very least I'd propose the module name to be rational. The code is really old. > * How many of __trunc__, __floor__, __ceil__, and __round__ should be > magic methods? I'm okay with all of these. > For __round__, when do we want to return an Integral? When the second argument is absent only. > [__properfraction__ is probably subsumed by divmod(x, 1).] Probably, but see PEP 3100, which still lists __mod__ and __divmod__ as to be deleted. > * How to give the removed methods (divmod, etc. on complex) good error > messages without having them show up in help(complex)? If Complex doesn't define them, they'll be TypeErrors, and that's good enough IMO. > I'll look into this during the sprint. > > On 8/2/07, Jeffrey Yasskin <[EMAIL PROTECTED]> wrote: > > After some more discussion, I have another version of the PEP with a > > draft, partial implementation. Let me know what you think. > > > > > > > > PEP: 3141 > > Title: A Type Hierarchy for Numbers > > Version: $Revision: 56646 $ > > Last-Modified: $Date: 2007-08-01 10:11:55 -0700 (Wed, 01 Aug 2007) $ > > Author: Jeffrey Yasskin <[EMAIL PROTECTED]> > > Status: Draft > > Type: Standards Track > > Content-Type: text/x-rst > > Created: 23-Apr-2007 > > Post-History: 25-Apr-2007, 16-May-2007, 02-Aug-2007 > > > > > > Abstract > > ======== > > > > This proposal defines a hierarchy of Abstract Base Classes (ABCs) (PEP > > 3119) to represent number-like classes. It proposes a hierarchy of > > ``Number :> Complex :> Real :> Rational :> Integral`` where ``A :> B`` > > means "A is a supertype of B", and a pair of ``Exact``/``Inexact`` > > classes to capture the difference between ``floats`` and > > ``ints``. These types are significantly inspired by Scheme's numeric > > tower [#schemetower]_. > > > > Rationale > > ========= > > > > Functions that take numbers as arguments should be able to determine > > the properties of those numbers, and if and when overloading based on > > types is added to the language, should be overloadable based on the > > types of the arguments. For example, slicing requires its arguments to > > be ``Integrals``, and the functions in the ``math`` module require > > their arguments to be ``Real``. > > > > Specification > > ============= > > > > This PEP specifies a set of Abstract Base Classes, and suggests a > > general strategy for implementing some of the methods. It uses > > terminology from PEP 3119, but the hierarchy is intended to be > > meaningful for any systematic method of defining sets of classes. > > > > The type checks in the standard library should use these classes > > instead of the concrete built-ins. > > > > > > Numeric Classes > > --------------- > > > > We begin with a Number class to make it easy for people to be fuzzy > > about what kind of number they expect. This class only helps with > > overloading; it doesn't provide any operations. :: > > > > class Number(metaclass=ABCMeta): pass > > > > > > Most implementations of complex numbers will be hashable, but if you > > need to rely on that, you'll have to check it explicitly: mutable > > numbers are supported by this hierarchy. **Open issue:** Should > > __pos__ coerce the argument to be an instance of the type it's defined > > on? Why do the builtins do this? :: > > > > class Complex(Number): > > """Complex defines the operations that work on the builtin complex > > type. > > > > In short, those are: a conversion to complex, .real, .imag, +, -, > > *, /, abs(), .conjugate, ==, and !=. > > > > If it is given heterogenous arguments, and doesn't have special > > knowledge about them, it should fall back to the builtin complex > > type as described below. > > """ > > > > @abstractmethod > > def __complex__(self): > > """Return a builtin complex instance.""" > > > > def __bool__(self): > > """True if self != 0.""" > > return self != 0 > > > > @abstractproperty > > def real(self): > > """Retrieve the real component of this number. > > > > This should subclass Real. > > """ > > raise NotImplementedError > > > > @abstractproperty > > def imag(self): > > """Retrieve the real component of this number. > > > > This should subclass Real. > > """ > > raise NotImplementedError > > > > @abstractmethod > > def __add__(self, other): > > raise NotImplementedError > > > > @abstractmethod > > def __radd__(self, other): > > raise NotImplementedError > > > > @abstractmethod > > def __neg__(self): > > raise NotImplementedError > > > > def __pos__(self): > > return self > > > > def __sub__(self, other): > > return self + -other > > > > def __rsub__(self, other): > > return -self + other > > > > @abstractmethod > > def __mul__(self, other): > > raise NotImplementedError > > > > @abstractmethod > > def __rmul__(self, other): > > raise NotImplementedError > > > > @abstractmethod > > def __div__(self, other): > > raise NotImplementedError > > > > @abstractmethod > > def __rdiv__(self, other): > > raise NotImplementedError > > > > @abstractmethod > > def __pow__(self, exponent): > > """Like division, a**b should promote to complex when > > necessary.""" > > raise NotImplementedError > > > > @abstractmethod > > def __rpow__(self, base): > > raise NotImplementedError > > > > @abstractmethod > > def __abs__(self): > > """Returns the Real distance from 0.""" > > raise NotImplementedError > > > > @abstractmethod > > def conjugate(self): > > """(x+y*i).conjugate() returns (x-y*i).""" > > raise NotImplementedError > > > > @abstractmethod > > def __eq__(self, other): > > raise NotImplementedError > > > > def __ne__(self, other): > > return not (self == other) > > > > > > The ``Real`` ABC indicates that the value is on the real line, and > > supports the operations of the ``float`` builtin. Real numbers are > > totally ordered except for NaNs (which this PEP basically ignores). :: > > > > class Real(Complex): > > """To Complex, Real adds the operations that work on real numbers. > > > > In short, those are: a conversion to float, trunc(), divmod, > > %, <, <=, >, and >=. > > > > Real also provides defaults for the derived operations. > > """ > > > > @abstractmethod > > def __float__(self): > > """Any Real can be converted to a native float object.""" > > raise NotImplementedError > > > > @abstractmethod > > def __trunc__(self): > > """Truncates self to an Integral. > > > > Returns an Integral i such that: > > * i>0 iff self>0 > > * abs(i) <= abs(self). > > """ > > raise NotImplementedError > > > > def __divmod__(self, other): > > """The pair (self // other, self % other). > > > > Sometimes this can be computed faster than the pair of > > operations. > > """ > > return (self // other, self % other) > > > > def __rdivmod__(self, other): > > """The pair (self // other, self % other). > > > > Sometimes this can be computed faster than the pair of > > operations. > > """ > > return (other // self, other % self) > > > > @abstractmethod > > def __floordiv__(self, other): > > """The floor() of self/other. Integral.""" > > raise NotImplementedError > > > > @abstractmethod > > def __rfloordiv__(self, other): > > """The floor() of other/self.""" > > raise NotImplementedError > > > > @abstractmethod > > def __mod__(self, other): > > raise NotImplementedError > > > > @abstractmethod > > def __rmod__(self, other): > > raise NotImplementedError > > > > @abstractmethod > > def __lt__(self, other): > > """< on Reals defines a total ordering, except perhaps for > > NaN.""" > > raise NotImplementedError > > > > @abstractmethod > > def __le__(self, other): > > raise NotImplementedError > > > > # Concrete implementations of Complex abstract methods. > > > > def __complex__(self): > > return complex(float(self)) > > > > @property > > def real(self): > > return self > > > > @property > > def imag(self): > > return 0 > > > > def conjugate(self): > > """Conjugate is a no-op for Reals.""" > > return self > > > > > > There is no built-in rational type, but it's straightforward to write, > > so we provide an ABC for it. **Open issue**: Add Demo/classes/Rat.py > > to the stdlib? :: > > > > class Rational(Real, Exact): > > """.numerator and .denominator should be in lowest terms.""" > > > > @abstractproperty > > def numerator(self): > > raise NotImplementedError > > > > @abstractproperty > > def denominator(self): > > raise NotImplementedError > > > > # Concrete implementation of Real's conversion to float. > > > > def __float__(self): > > return self.numerator / self.denominator > > > > > > And finally integers:: > > > > class Integral(Rational): > > """Integral adds a conversion to int and the bit-string > > operations.""" > > > > @abstractmethod > > def __int__(self): > > raise NotImplementedError > > > > def __index__(self): > > return int(self) > > > > @abstractmethod > > def __pow__(self, exponent, modulus): > > """self ** exponent % modulus, but maybe faster. > > > > Implement this if you want to support the 3-argument version > > of pow(). Otherwise, just implement the 2-argument version > > described in Complex. Raise a TypeError if exponent < 0 or any > > argument isn't Integral. > > """ > > raise NotImplementedError > > > > @abstractmethod > > def __lshift__(self, other): > > raise NotImplementedError > > > > @abstractmethod > > def __rlshift__(self, other): > > raise NotImplementedError > > > > @abstractmethod > > def __rshift__(self, other): > > raise NotImplementedError > > > > @abstractmethod > > def __rrshift__(self, other): > > raise NotImplementedError > > > > @abstractmethod > > def __and__(self, other): > > raise NotImplementedError > > > > @abstractmethod > > def __rand__(self, other): > > raise NotImplementedError > > > > @abstractmethod > > def __xor__(self, other): > > raise NotImplementedError > > > > @abstractmethod > > def __rxor__(self, other): > > raise NotImplementedError > > > > @abstractmethod > > def __or__(self, other): > > raise NotImplementedError > > > > @abstractmethod > > def __ror__(self, other): > > raise NotImplementedError > > > > @abstractmethod > > def __invert__(self): > > raise NotImplementedError > > > > # Concrete implementations of Rational and Real abstract methods. > > > > def __float__(self): > > return float(int(self)) > > > > @property > > def numerator(self): > > return self > > > > @property > > def denominator(self): > > return 1 > > > > > > Exact vs. Inexact Classes > > ------------------------- > > > > Floating point values may not exactly obey several of the properties > > you would expect. For example, it is possible for ``(X + -X) + 3 == > > 3``, but ``X + (-X + 3) == 0``. On the range of values that most > > functions deal with this isn't a problem, but it is something to be > > aware of. > > > > Therefore, I define ``Exact`` and ``Inexact`` ABCs to mark whether > > types have this problem. Every instance of ``Integral`` and > > ``Rational`` should be Exact, but ``Reals`` and ``Complexes`` may or > > may not be. (Do we really only need one of these, and the other is > > defined as ``not`` the first?) :: > > > > class Exact(Number): pass > > class Inexact(Number): pass > > > > > > Changes to operations and __magic__ methods > > ------------------------------------------- > > > > To support more precise narrowing from float to int (and more > > generally, from Real to Integral), I'm proposing the following new > > __magic__ methods, to be called from the corresponding library > > functions. All of these return Integrals rather than Reals. > > > > 1. ``__trunc__(self)``, called from a new builtin ``trunc(x)``, which > > returns the Integral closest to ``x`` between 0 and ``x``. > > > > 2. ``__floor__(self)``, called from ``math.floor(x)``, which returns > > the greatest Integral ``<= x``. > > > > 3. ``__ceil__(self)``, called from ``math.ceil(x)``, which returns the > > least Integral ``>= x``. > > > > 4. ``__round__(self)``, called from ``round(x)``, with returns the > > Integral closest to ``x``, rounding half toward even. **Open > > issue:** We could support the 2-argument version, but then we'd > > only return an Integral if the second argument were ``<= 0``. > > > > 5. ``__properfraction__(self)``, called from a new function, > > ``math.properfraction(x)``, which resembles C's ``modf()``: returns > > a pair ``(n:Integral, r:Real)`` where ``x == n + r``, both ``n`` > > and ``r`` have the same sign as ``x``, and ``abs(r) < 1``. **Open > > issue:** Oh, we already have ``math.modf``. What name do we want > > for this? Should we use divmod(x, 1) instead? > > > > Because the ``int()`` conversion from ``float`` is equivalent to but > > less explicit than ``trunc()``, let's remove it. (Or, if that breaks > > too much, just add a deprecation warning.) > > > > ``complex.__{divmod,mod,floordiv,int,float}__`` should also go > > away. These should continue to raise ``TypeError`` to help confused > > porters, but should not appear in ``help(complex)`` to avoid confusing > > more people. **Open issue:** This is difficult to do with the > > ``PyNumberMethods`` struct. What's the best way to accomplish it? > > > > > > Notes for type implementors > > --------------------------- > > > > Implementors should be careful to make equal numbers equal and > > hash them to the same values. This may be subtle if there are two > > different extensions of the real numbers. For example, a complex type > > could reasonably implement hash() as follows:: > > > > def __hash__(self): > > return hash(complex(self)) > > > > but should be careful of any values that fall outside of the built in > > complex's range or precision. > > > > Adding More Numeric ABCs > > ~~~~~~~~~~~~~~~~~~~~~~~~ > > > > There are, of course, more possible ABCs for numbers, and this would > > be a poor hierarchy if it precluded the possibility of adding > > those. You can add ``MyFoo`` between ``Complex`` and ``Real`` with:: > > > > class MyFoo(Complex): ... > > MyFoo.register(Real) > > > > Implementing the arithmetic operations > > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > > > > We want to implement the arithmetic operations so that mixed-mode > > operations either call an implementation whose author knew about the > > types of both arguments, or convert both to the nearest built in type > > and do the operation there. For subtypes of Integral, this means that > > __add__ and __radd__ should be defined as:: > > > > class MyIntegral(Integral): > > > > def __add__(self, other): > > if isinstance(other, MyIntegral): > > return do_my_adding_stuff(self, other) > > elif isinstance(other, OtherTypeIKnowAbout): > > return do_my_other_adding_stuff(self, other) > > else: > > return NotImplemented > > > > def __radd__(self, other): > > if isinstance(other, MyIntegral): > > return do_my_adding_stuff(other, self) > > elif isinstance(other, OtherTypeIKnowAbout): > > return do_my_other_adding_stuff(other, self) > > elif isinstance(other, Integral): > > return int(other) + int(self) > > elif isinstance(other, Real): > > return float(other) + float(self) > > elif isinstance(other, Complex): > > return complex(other) + complex(self) > > else: > > return NotImplemented > > > > > > There are 5 different cases for a mixed-type operation on subclasses > > of Complex. I'll refer to all of the above code that doesn't refer to > > MyIntegral and OtherTypeIKnowAbout as "boilerplate". ``a`` will be an > > instance of ``A``, which is a subtype of ``Complex`` (``a : A <: > > Complex``), and ``b : B <: Complex``. I'll consider ``a + b``: > > > > 1. If A defines an __add__ which accepts b, all is well. > > 2. If A falls back to the boilerplate code, and it were to return > > a value from __add__, we'd miss the possibility that B defines > > a more intelligent __radd__, so the boilerplate should return > > NotImplemented from __add__. (Or A may not implement __add__ at > > all.) > > 3. Then B's __radd__ gets a chance. If it accepts a, all is well. > > 4. If it falls back to the boilerplate, there are no more possible > > methods to try, so this is where the default implementation > > should live. > > 5. If B <: A, Python tries B.__radd__ before A.__add__. This is > > ok, because it was implemented with knowledge of A, so it can > > handle those instances before delegating to Complex. > > > > If ``A<:Complex`` and ``B<:Real`` without sharing any other knowledge, > > then the appropriate shared operation is the one involving the built > > in complex, and both __radd__s land there, so ``a+b == b+a``. > > > > > > Rejected Alternatives > > ===================== > > > > The initial version of this PEP defined an algebraic hierarchy > > inspired by a Haskell Numeric Prelude [#numericprelude]_ including > > MonoidUnderPlus, AdditiveGroup, Ring, and Field, and mentioned several > > other possible algebraic types before getting to the numbers. I had > > expected this to be useful to people using vectors and matrices, but > > the NumPy community really wasn't interested, and we ran into the > > issue that even if ``x`` is an instance of ``X <: MonoidUnderPlus`` > > and ``y`` is an instance of ``Y <: MonoidUnderPlus``, ``x + y`` may > > still not make sense. > > > > Then I gave the numbers a much more branching structure to include > > things like the Gaussian Integers and Z/nZ, which could be Complex but > > wouldn't necessarily support things like division. The community > > decided that this was too much complication for Python, so I've now > > scaled back the proposal to resemble the Scheme numeric tower much > > more closely. > > > > > > References > > ========== > > > > .. [#pep3119] Introducing Abstract Base Classes > > (http://www.python.org/dev/peps/pep-3119/) > > > > .. [#classtree] Possible Python 3K Class Tree?, wiki page created by > > Bill Janssen > > (http://wiki.python.org/moin/AbstractBaseClasses) > > > > .. [#numericprelude] NumericPrelude: An experimental alternative > > hierarchy of numeric type classes > > (http://darcs.haskell.org/numericprelude/docs/html/index.html) > > > > .. [#schemetower] The Scheme numerical tower > > > > (http://www.swiss.ai.mit.edu/ftpdir/scheme-reports/r5rs-html/r5rs_8.html#SEC50) > > > > > > Acknowledgements > > ================ > > > > Thanks to Neil Norwitz for encouraging me to write this PEP in the > > first place, to Travis Oliphant for pointing out that the numpy people > > didn't really care about the algebraic concepts, to Alan Isaac for > > reminding me that Scheme had already done this, and to Guido van > > Rossum and lots of other people on the mailing list for refining the > > concept. > > > > Copyright > > ========= > > > > This document has been placed in the public domain. > > > > > > > > .. > > Local Variables: > > mode: indented-text > > indent-tabs-mode: nil > > sentence-end-double-space: t > > fill-column: 70 > > coding: utf-8 > > End: > > > > > > > -- > Namasté, > Jeffrey Yasskin > http://jeffrey.yasskin.info/ > > "Religion is an improper response to the Divine." — "Skinny Legs and > All", by Tom Robbins > _______________________________________________ > Python-3000 mailing list > Python-3000@python.org > http://mail.python.org/mailman/listinfo/python-3000 > Unsubscribe: > http://mail.python.org/mailman/options/python-3000/guido%40python.org > -- --Guido van Rossum (home page: http://www.python.org/~guido/) _______________________________________________ Python-3000 mailing list Python-3000@python.org http://mail.python.org/mailman/listinfo/python-3000 Unsubscribe: http://mail.python.org/mailman/options/python-3000/archive%40mail-archive.com