Author robert_smallshire
Recipients Austin Bingham, christian.heimes, facundobatista, mark.dickinson, rhettinger, robert_smallshire, serhiy.storchaka, skrah, tim.peters
Date 2018-03-12.14:42:43
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <CAHYrD20Hn_NkgiW2kSo0Ht8duGia0wHRFdeC7Su=iRuDpKv-vw@mail.gmail.com>
In-reply-to <1520744283.02.0.467229070634.issue26680@psf.upfronthosting.co.za>
Content
To respond to Raymond's points:

1) Nobody is suggesting that every float method should also be available on
int.  Clearly some methods on float are not applicable to int.

2) Taken narrowly, you're right that is_integer() does nothing useful for
int.  Neither do imag, denominator, __floor__, or __trunc__. They're all
present so we can successfully use integers in duck-typing situations along
with the other number types. My claim is that int.is_integer() is useful
for the same reasons.

The problem isn't that you or I don't know that we should write a == int(a)
to be portable, the problem is that code that *already* uses x.is_integer()
fails hard when x happens to be an int. As I've demonstrated, some built-in
operators and functions can return either int or float, even when the
operand types are all int, depending only on the *values* of the operands.

This wouldn't matter if nobody ever wrote f.is_integer(), and instead used
the trivially portable solution, but they do, and at your behest: In 2011
you published "f.is_integer() is the new way to test whether a float is
integral. The old way, x==int(x), is history."  The reality is that folks
often write Python functions which accept *numbers*, without giving too
much thought to whether calling my_func(42.0) will work, but my_func(42)
will cause an unhandled exception that nobody is expecting. Indeed, one of
the joys of Python is that we often don't need to overthink this.

The trivial portable solution is also nearly three times slower than
float.is_integer() and int.is_integer(). Moreso if you package it up in a
function so it can be applied to more complex expressions in, say, a
comprehension, where an intermediate assignment is not possible.

I'm not the only person to be thrown by this. See this:

    is_integer() not working: https://stackoverflow.com/
questions/17170226/is-integer-not-working/17170511

and this:

   https://www.reddit.com/r/learnpython/comments/4tp4hy/
need_help_with_identify_number_as_integer/

and this

    https://wiesmann.codiferes.net/wordpress/?p=13366

Furthermore, once the is_integer() method is discovered, it leads to folks
writing odd code in order to leverage it, such as always converting user
integer input to float in order to check that it's really is an integer
(which of course subtly limits the precision of allowable integers).
There's an example of this on page 14 of the book *Doing Math With Python*.
https://www.amazon.com/Doing-Math-Python-Programming-Statistics/dp/1593276400

Other prolific and widely respected Python programmers have described this
behaviour as "kind of nuts" (though I'm not going to involve them here).
The behaviour has also invited unfortunate comparisons with similar
non-obvious behaviour in Javascript.

3) I'd be very surprised if the presence of this method on int caused any
more confusion, or impediment to learning than do the presence of int.imag
or int.denominator.

4) I'm less bothered about the numeric tower aspect than I am about
duck-type compatibility between the built-in types int and float. That
said, a key part of what I think is your concern about creating work for
subclass implementers can be addressed by providing a default
implementation Real.is_integer() in terms of int(x) == x.

5) The decimal spec doesn't require an is_decimal function, but it doesn't
forbid it either. In any case, the Decimal implementation already
implements is_integer internally as `cpx_mpd_isinteger` – and uses it a
great deal, which demonstrates its utility. My patch simply exposes it to
Python. There's no danger of violating any specification, unless that
specification says that you must not implement a method called is_integer,
which it doesn't, especially as we would be using a definition which is
already de facto compatible with the standard.  I don't care very much
about Decimal either for my own work, especially as it already stands apart
from the numeric tower. I implemented it to be consistent with my argument
about duck typed numbers (which still largely holds for Decimal, except for
floor division and modulus I believe).

Solutions for which use `a == int(a)` or `a == a.to_integral_value()` fail
for NaN and infinities, whereas float.is_integer() is more robust. It turns
out the trivial portable solution isn't so trivial, or so portable, if
implemented robustly, performantly and with duck-typing in mind.

Ultimately, my argument is one about duck typing across numbers types. If
if that abstraction isn't valued, I have nowhere to go.

*Robert Smallshire | *Managing Director
*Sixty North* | Applications | Consulting | Training
rob@sixty-north.com | T +47 63 01 04 44 | M +47 924 30 350
http://sixty-north.com

On 11 March 2018 at 05:58, Raymond Hettinger <report@bugs.python.org> wrote:

>
> Raymond Hettinger <raymond.hettinger@gmail.com> added the comment:
>
> Sorry Robert, but I object to this going forward.
>
> 1) We do not and should not require that every float() method also be in
> int():
>
>    >>> set(dir(float)) - set(dir(int))
>    {'fromhex', 'hex', 'is_integer', '__getformat__', '__setformat__',
>     'as_integer_ratio'}
>
> 2) Your use case is trivially solved in a portable, trivial, and readable
> way:
>
>    a == int(a)
>
> 3) I really don't want to clutter the other types with this method when it
> does nothing useful for those types.  In particular, I expect that the
> presence of "is_integer()" in the int() class will likely create more
> confusion than it solves (perhaps not for you, but for the vast majority of
> users, none of whom have ever requested this behavior over the entire
> history of the language).
>
> 4) Also, I don't what this to have to propagate to every project that ever
> registers their custom numeric types with the numeric tower.  Adding this
> method to the tower is essentially making a requirement that everyone,
> everywhere must add this method.   That is not in the spirit of what the
> ABCs are all about -- they mostly require a small and useful subset of the
> behaviors of the related concrete classes (i.e. the concrete collections
> all have more methods than are required by their collections.abc
> counterparts).
>
> 5) Lastly, the spirit of the decimal module was to stick as closely as
> possible to the decimal specification and assiduously avoid extending the
> spec with new inventions (risking duplication of functionality, risking
> non-portability with other implementations, risking not handling special
> values in a way that is consistent with the spec, risking our going down a
> path that intentionally not chosen by the spec creators, or risking being
> at odds with subsequent updates to the spec).
>
> ----------
>
> _______________________________________
> Python tracker <report@bugs.python.org>
> <https://bugs.python.org/issue26680>
> _______________________________________
>
History
Date User Action Args
2018-03-12 14:42:44robert_smallshiresetrecipients: + robert_smallshire, tim.peters, rhettinger, facundobatista, mark.dickinson, christian.heimes, skrah, serhiy.storchaka, Austin Bingham
2018-03-12 14:42:44robert_smallshirelinkissue26680 messages
2018-03-12 14:42:43robert_smallshirecreate