Split out integer functions from the math module

For users of linters, yes. Every time I upgrade minimum Python version for a project, I run Ruff to automatically upgrade everything to be most idiomatic.

Right, the switch happens when you increase the Python minimum version.

No one needs try-except like that. The switch doesn’t happen until the minimum Python version guarantees that imath has the functions you want.

2 Likes

Small ints, yes.

My point was, that this “work” isn’t too helpful. Simple benchmark:

prod(i): Mean +- std dev: 389 us +- 24 us
reduce(mul, i, 1): Mean +- std dev: 384 us +- 0 us
prod(f): Mean +- std dev: 3.09 us +- 0.11 us
reduce(mul, f, 1): Mean +- std dev: 8.12 us +- 0.31 us
prod(c): Mean +- std dev: 13.8 us +- 0.0 us
reduce(mul, c, 1): Mean +- std dev: 13.0 us +- 0.1 us
prod(r): Mean +- std dev: 2.25 ms +- 0.01 ms
reduce(mul, r, 1): Mean +- std dev: 2.28 ms +- 0.01 ms
prod(d): Mean +- std dev: 87.0 us +- 1.2 us
reduce(mul, d, 1): Mean +- std dev: 87.6 us +- 1.0 us

There is even a speed loss for complex or bigints (which is rather artificIal example). So, really these “generics” do something helpful only for the libm’s-style math module.

import pyperf

import fractions
import decimal
import random
from operator import mul
from functools import reduce
from math import prod

random.seed(1)
data = [random.random() for _ in range(100)]
f = data.copy()
c = [complex(_) for _ in data]
r = [fractions.Fraction(_) for _ in data]
d = [decimal.Decimal(_) for _ in data]
i = [random.randint(1<<100, 1<<128) for _ in range(100)]

runner = pyperf.Runner()

runner.bench_func('prod(i)', prod, i)
runner.bench_func('reduce(mul, i, 1)', reduce, mul, i, 1)

runner.bench_func('prod(c)', prod, c)
runner.bench_func('reduce(mul, c, 1)', reduce, mul, c, 1)

runner.bench_func('prod(r)', prod, r)
runner.bench_func('reduce(mul, r, 1)', reduce, mul, r, 1)

runner.bench_func('prod(d)', prod, d)
runner.bench_func('reduce(mul, d, 1)', reduce, mul, d, 1)

JFR, this it something I was thinking of. Cons of putting this in “Open Issues” of the PEP is that it’s yet another minor point to debate;-) But maybe it worth?

Currently we have both (1) some int’s methods and (2) few functions in the math. For me it seems, that (2) offer more generic API (works for int-like objects) and (1) is for more basic operations. Maybe it’s a good idea to keep (1) being minimal. (E.g. gmpy2’s mpz API-is compatible with the int, but not all functions from (2) are implemented.) But in the end — this distinction is more or less arbitrary.

It will save us from future littering.

Initially, addition of the imath module was proposed when only factorial() gcd() were in math. And math.factorial() accepted float, while imath.factorial() would only accept integers. Second time it was proposed when isqrt(), comb() and prod() was added im math. They could be added in imath instead, so no littering. Then math.lcm() and int.bit_count() was added, they could be in imath. The more we postpone this decision, the more the module becomes littered, the more difficult the future transition will be.

Currently, 6 integer specific functions take up a third of the code in the math module. If we add 6 more, it could be half. They literally litter the code from a maintainer’s perspective, not only from a user’s perspective.

7 Likes

It’s not an “open issue” for the PEP since the PEP is recommending against that. It’s rejected ideas, at best.

Also, making any of these functions methods of int goes against at least some OO textbook principles, namely that:

  • the interface of a class should not contain methods that could be implemented using the public interface of the class; and,
  • monolithic classes should be avoided.

Python doesn’t always follow this, but it typically only creates methods for things that

  • can’t be implemented using the public interface (required), or
  • are sufficiently popular that adding an import would be too annoying (e.g., str.format).
1 Like

As a maintainer, though not of math.c, this convinces me.

2 Likes

I don’t think so. I’m the only one who mentioned it, and added that there “are reasons” for making these things functions instead. There really hasn’t been any argument.

Indeed, if we had had an imath module all along, I expect int.bit_length() and int.bit_count() would have ended up there instead.

3 Likes

Literal numbers and methods do not play well together. 37.bit_count() raises “SyntaxError: invalid decimal literal”. One must either insert a space, 37 .bit_count(), or wrap with parentheses, (37).bit_count(), or call the function directly, int.bit_count(37). Same would be true of calling methods on floats. [edit: not true]

2 Likes

Can you clarify that? For example 1.5.as_integer_ratio() and
1..as_integer_ratio() and 1e2.as_integer_ratio() all work fine.

1 Like

It’s still fiddly. I think most people don’t realize that negative numeric literals don’t exist in Python’s grammar. So, e.g.,

>>> 1..as_integer_ratio()
(1, 1)
>>> -1..as_integer_ratio()
Traceback (most recent call last):
    ...
TypeError: bad operand type for unary -: 'tuple'

Putting a space between the periods doesn’t help with that either - parens are needed to force the intended order. Same for ints:

>>> -1 .bit_count()
-1
>>> (-1) .bit_count()
1

Maybe that’s one for @Rosuav’s next Python quiz :wink:.

5 Likes

Dangit, I just made one :rofl: That would actually have been a good one to include.

1 Like

You are right. Added error note. Not sure is this changed or I just mis-remembered.

1 Like

It is now a rule that new (top-level) modules (and new builtins) require a PEP, see PEP 2 – Procedure for Adding New Modules | peps.python.org

1 Like

Sorry, late to the discussion. As an instructor, I disagree that "It doesn’t matter if beginners look there”. It’s very important that beginners get a language that reflects their knowledge of math. High school doesn’t (generally) teach ints versus floats. It’s only us computer people that worry about those.
To beginners, number / number gives you an answer - knowing about ints and floats at that stage muddies the waters. Python 3 made a big shift when real division was introduced, and helps beginners enormously.

3 Likes

I think you should go to the new PEP thread (I did update of the description).

Is that true? I think that at least integers, rationals and fixed-point arithmetic (decimal, of course) — parts of basic education in most countries.

This “answer” below should be meaningless for beginners, assuming declared above level of knowledge (no floats):

>>> 2/200000
1e-05

c.f. 1/100000.

:+1:

@moderators Please lock this thread so we continue discussion in the new thread: PEP 791: imath --- module for integer-specific mathematics functions

2 Likes