[Python-Dev] Symmetry arguments for API expansion

Raymond Hettinger raymond.hettinger at gmail.com
Mon Mar 12 12:49:27 EDT 2018

There is a feature request and patch to propagate the float.is_integer() API through rest of the numeric types ( https://bugs.python.org/issue26680 ).

While I don't think it is a good idea, the OP has been persistent and wants his patch to go forward.  

It may be worthwhile to discuss on this list to help resolve this particular request and to address the more general, recurring design questions. Once a feature with a marginally valid use case is added to an API, it is common for us to get downstream requests to propagate that API to other places where it makes less sense but does restore a sense of symmetry or consistency.  In cases where an abstract base class is involved, acceptance of the request is usually automatic (i.e. range() and tuple() objects growing index() and count() methods).  However, when our hand hasn't been forced, there is still an opportunity to decline.  That said, proponents of symmetry requests tend to feel strongly about it and tend to never fully accept such a request being declined (it leaves them with a sense that Python is disordered and unbalanced).


---- My thoughts on the feature request -----

What is the proposal?
* Add an is_integer() method to int(), Decimal(), Fraction(), and Real(). Modify Rational() to provide a default implementation.

Starting point: Do we need this?
* We already have a simple, traditional, portable, and readable way to make the test:  int(x) == x
* In the context of ints, the test x.is_integer() always returns True.  This isn't very useful.
* Aside from the OP, this behavior has never been requested in Python's 27 year history.

Does it cost us anything?
* Yes, adding a method to the numeric tower makes it a requirement for every class that ever has or ever will register or inherit from the tower ABCs.
* Adding methods to a core object such as int() increases the cognitive load for everyday users who look at dir(), call help(), or read the main docs.
* It conflicts with a design goal for the decimal module to not invent new functionality beyond the spec unless essential for integration with the rest of the language.  The reasons included portability with other implementations and not trying to guess what the committee would have decided in the face of tricky questions such as whether Decimal('1.000001').is_integer()
should return True when the context precision is only three decimal places (i.e. whether context precision and rounding traps should be applied before the test and whether context flags should change after the test).

Shouldn't everything in a concrete class also be in an ABC and all its subclasses?
* In general, the answer is no.  The ABCs are intended to span only basic functionality.  For example, GvR intentionally omitted update() from the Set() ABC because the need was fulfilled by __ior__().

But int() already has real, imag, numerator, and denominator, why is this different?
* Those attributes are central to the functioning of the numeric tower.
* In contrast, the is_integer() method is a peripheral and incidental concept.

What does "API Parsimony" mean?
* Avoidance of feature creep.
* Preference for only one obvious way to do things.
* Practicality (not craving things you don't really need) beats purity (symmetry and foolish consistency).
* YAGNI suggests holding off in the absence of clear need.
* Recognition that smaller APIs are generally better for users.

Are there problems with symmetry/consistency arguments?
* The need for guard rails on an overpass doesn't imply the same need on a underpass even though both are in the category of grade changing byways.
* "In for a penny, in for a pound" isn't a principle of good design; rather, it is a slippery slope whereby the acceptance of a questionable feature in one place seems to compel later decisions to propagate the feature to other places where the cost / benefit trade-offs are less favorable.

Should float.as_integer() have ever been added in the first place?
* Likely, it should have been a math module function like isclose() and isinf() so that it would not have been type specific.
* However, that ship has sailed; instead, the question is whether we now have to double down and have to dispatch other ships as well.
* There is some question as to whether it is even a good idea to be testing the results of floating point calculations for exact values. It may be useful for testing inputs, but is likely a trap for people using it other contexts.

Have we ever had problems with just accepting requests solely based on symmetry?
* Yes.  The str.startswith() and str.endswith() methods were given optional start/end arguments to be consistent with str.index(), not because there were known use cases where code was made better with the new feature.   This ended up conflicting with a later feature request that did have valid use cases (supporting multiple test prefixes/suffixes).  As a result, we ended-up with an awkward and error-prone API that requires double parenthesis for the valid use case:  url.endswith(('.html', '.css')).

More information about the Python-Dev mailing list