wolph / numpy-stl

Simple library to make working with STL files (and 3D objects in general) fast and easy.
http://numpy-stl.readthedocs.org/
BSD 3-Clause "New" or "Revised" License
628 stars 107 forks source link

Python 3.11: random.randint(0, 1e6): TypeError: 'float' object cannot be interpreted as an integer #188

Closed hroncok closed 2 years ago

hroncok commented 2 years ago

Hello @WoLpH.

We are building all Fedora packages with Python 3.11 pre-releases to figure out all the incompatibilities early in the development cycle of the new Python version. With numpy-stl 2.16.3 and Python 3.11.0a4, we see the following errors:

=================================== FAILURES ===================================
_______________________________ test_main[False] _______________________________

ascii_file = '/builddir/build/BUILD/numpy-stl-2.16.3/tests/stl_ascii/HalfDonut.stl'
binary_file = '/builddir/build/BUILD/numpy-stl-2.16.3/tests/stl_binary/HalfDonut.stl'
tmpdir = local('/tmp/pytest-of-mockbuild/pytest-0/test_main_False_0')
speedups = False

    def test_main(ascii_file, binary_file, tmpdir, speedups):
        original_argv = sys.argv[:]
        args_pre = ['stl']
        args_post = [str(tmpdir.join('output.stl'))]

        if not speedups:
            args_pre.append('-s')

        try:
            sys.argv[:] = args_pre + [ascii_file] + args_post
>           main.main()

tests/test_commandline.py:16: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stl/main.py:48: in main
    name = _get_name(args)
stl/main.py:32: in _get_name
    'numpy-stl-%06d' % random.randint(0, 1e6),
/usr/lib64/python3.11/random.py:330: in randint
    return self.randrange(a, b+1)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <random.Random object at 0x55774b8355d0>, start = 0, stop = 1000001.0
step = 1

    def randrange(self, start, stop=None, step=_ONE):
        """Choose a random item from range(stop) or range(start, stop[, step]).

        Roughly equivalent to ``choice(range(start, stop, step))``
        but supports arbitrarily large ranges and is optimized
        for common cases.

        """

        # This code is a bit messy to make it fast for the
        # common case while still doing adequate error checking.
        istart = _index(start)
        if stop is None:
            # We don't check for "step != 1" because it hasn't been
            # type checked and converted to an integer yet.
            if step is not _ONE:
                raise TypeError('Missing a non-None stop argument')
            if istart > 0:
                return self._randbelow(istart)
            raise ValueError("empty range for randrange()")

        # Stop argument supplied.
>       istop = _index(stop)
E       TypeError: 'float' object cannot be interpreted as an integer

/usr/lib64/python3.11/random.py:306: TypeError
_______________________________ test_main[True] ________________________________

ascii_file = '/builddir/build/BUILD/numpy-stl-2.16.3/tests/stl_ascii/HalfDonut.stl'
binary_file = '/builddir/build/BUILD/numpy-stl-2.16.3/tests/stl_binary/HalfDonut.stl'
tmpdir = local('/tmp/pytest-of-mockbuild/pytest-0/test_main_True_0')
speedups = True

    def test_main(ascii_file, binary_file, tmpdir, speedups):
        original_argv = sys.argv[:]
        args_pre = ['stl']
        args_post = [str(tmpdir.join('output.stl'))]

        if not speedups:
            args_pre.append('-s')

        try:
            sys.argv[:] = args_pre + [ascii_file] + args_post
>           main.main()

tests/test_commandline.py:16: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stl/main.py:48: in main
    name = _get_name(args)
stl/main.py:32: in _get_name
    'numpy-stl-%06d' % random.randint(0, 1e6),
/usr/lib64/python3.11/random.py:330: in randint
    return self.randrange(a, b+1)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <random.Random object at 0x55774b8355d0>, start = 0, stop = 1000001.0
step = 1

    def randrange(self, start, stop=None, step=_ONE):
        """Choose a random item from range(stop) or range(start, stop[, step]).

        Roughly equivalent to ``choice(range(start, stop, step))``
        but supports arbitrarily large ranges and is optimized
        for common cases.

        """

        # This code is a bit messy to make it fast for the
        # common case while still doing adequate error checking.
        istart = _index(start)
        if stop is None:
            # We don't check for "step != 1" because it hasn't been
            # type checked and converted to an integer yet.
            if step is not _ONE:
                raise TypeError('Missing a non-None stop argument')
            if istart > 0:
                return self._randbelow(istart)
            raise ValueError("empty range for randrange()")

        # Stop argument supplied.
>       istop = _index(stop)
E       TypeError: 'float' object cannot be interpreted as an integer

/usr/lib64/python3.11/random.py:306: TypeError
_______________________________ test_args[False] _______________________________

ascii_file = '/builddir/build/BUILD/numpy-stl-2.16.3/tests/stl_ascii/HalfDonut.stl'
tmpdir = local('/tmp/pytest-of-mockbuild/pytest-0/test_args_False_0')

    def test_args(ascii_file, tmpdir):
        parser = main._get_parser('')

        def _get_name(*args):
            return main._get_name(parser.parse_args(list(map(str, args))))

>       assert _get_name('--name', 'foobar') == 'foobar'

tests/test_commandline.py:33: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
tests/test_commandline.py:31: in _get_name
    return main._get_name(parser.parse_args(list(map(str, args))))
stl/main.py:32: in _get_name
    'numpy-stl-%06d' % random.randint(0, 1e6),
/usr/lib64/python3.11/random.py:330: in randint
    return self.randrange(a, b+1)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <random.Random object at 0x55774b8355d0>, start = 0, stop = 1000001.0
step = 1

    def randrange(self, start, stop=None, step=_ONE):
        """Choose a random item from range(stop) or range(start, stop[, step]).

        Roughly equivalent to ``choice(range(start, stop, step))``
        but supports arbitrarily large ranges and is optimized
        for common cases.

        """

        # This code is a bit messy to make it fast for the
        # common case while still doing adequate error checking.
        istart = _index(start)
        if stop is None:
            # We don't check for "step != 1" because it hasn't been
            # type checked and converted to an integer yet.
            if step is not _ONE:
                raise TypeError('Missing a non-None stop argument')
            if istart > 0:
                return self._randbelow(istart)
            raise ValueError("empty range for randrange()")

        # Stop argument supplied.
>       istop = _index(stop)
E       TypeError: 'float' object cannot be interpreted as an integer

/usr/lib64/python3.11/random.py:306: TypeError
_______________________________ test_args[True] ________________________________

ascii_file = '/builddir/build/BUILD/numpy-stl-2.16.3/tests/stl_ascii/HalfDonut.stl'
tmpdir = local('/tmp/pytest-of-mockbuild/pytest-0/test_args_True_0')

    def test_args(ascii_file, tmpdir):
        parser = main._get_parser('')

        def _get_name(*args):
            return main._get_name(parser.parse_args(list(map(str, args))))

>       assert _get_name('--name', 'foobar') == 'foobar'

tests/test_commandline.py:33: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
tests/test_commandline.py:31: in _get_name
    return main._get_name(parser.parse_args(list(map(str, args))))
stl/main.py:32: in _get_name
    'numpy-stl-%06d' % random.randint(0, 1e6),
/usr/lib64/python3.11/random.py:330: in randint
    return self.randrange(a, b+1)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <random.Random object at 0x55774b8355d0>, start = 0, stop = 1000001.0
step = 1

    def randrange(self, start, stop=None, step=_ONE):
        """Choose a random item from range(stop) or range(start, stop[, step]).

        Roughly equivalent to ``choice(range(start, stop, step))``
        but supports arbitrarily large ranges and is optimized
        for common cases.

        """

        # This code is a bit messy to make it fast for the
        # common case while still doing adequate error checking.
        istart = _index(start)
        if stop is None:
            # We don't check for "step != 1" because it hasn't been
            # type checked and converted to an integer yet.
            if step is not _ONE:
                raise TypeError('Missing a non-None stop argument')
            if istart > 0:
                return self._randbelow(istart)
            raise ValueError("empty range for randrange()")

        # Stop argument supplied.
>       istop = _index(stop)
E       TypeError: 'float' object cannot be interpreted as an integer

/usr/lib64/python3.11/random.py:306: TypeError
______________________________ test_ascii[False] _______________________________

binary_file = '/builddir/build/BUILD/numpy-stl-2.16.3/tests/stl_binary/HalfDonut.stl'
tmpdir = local('/tmp/pytest-of-mockbuild/pytest-0/test_ascii_False_0')
speedups = False

    def test_ascii(binary_file, tmpdir, speedups):
        original_argv = sys.argv[:]
        try:
            sys.argv[:] = [
                'stl',
                '-s' if not speedups else '',
                binary_file,
                str(tmpdir.join('ascii.stl')),
            ]
            try:
>               main.to_ascii()

tests/test_commandline.py:49: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stl/main.py:69: in to_ascii
    name = _get_name(args)
stl/main.py:32: in _get_name
    'numpy-stl-%06d' % random.randint(0, 1e6),
/usr/lib64/python3.11/random.py:330: in randint
    return self.randrange(a, b+1)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <random.Random object at 0x55774b8355d0>, start = 0, stop = 1000001.0
step = 1

    def randrange(self, start, stop=None, step=_ONE):
        """Choose a random item from range(stop) or range(start, stop[, step]).

        Roughly equivalent to ``choice(range(start, stop, step))``
        but supports arbitrarily large ranges and is optimized
        for common cases.

        """

        # This code is a bit messy to make it fast for the
        # common case while still doing adequate error checking.
        istart = _index(start)
        if stop is None:
            # We don't check for "step != 1" because it hasn't been
            # type checked and converted to an integer yet.
            if step is not _ONE:
                raise TypeError('Missing a non-None stop argument')
            if istart > 0:
                return self._randbelow(istart)
            raise ValueError("empty range for randrange()")

        # Stop argument supplied.
>       istop = _index(stop)
E       TypeError: 'float' object cannot be interpreted as an integer

/usr/lib64/python3.11/random.py:306: TypeError
______________________________ test_binary[False] ______________________________

ascii_file = '/builddir/build/BUILD/numpy-stl-2.16.3/tests/stl_ascii/HalfDonut.stl'
tmpdir = local('/tmp/pytest-of-mockbuild/pytest-0/test_binary_False_0')
speedups = False

    def test_binary(ascii_file, tmpdir, speedups):
        original_argv = sys.argv[:]
        try:
            sys.argv[:] = [
                'stl',
                '-s' if not speedups else '',
                ascii_file,
                str(tmpdir.join('binary.stl')),
            ]
            try:
>               main.to_binary()

tests/test_commandline.py:66: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stl/main.py:81: in to_binary
    name = _get_name(args)
stl/main.py:32: in _get_name
    'numpy-stl-%06d' % random.randint(0, 1e6),
/usr/lib64/python3.11/random.py:330: in randint
    return self.randrange(a, b+1)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <random.Random object at 0x55774b8355d0>, start = 0, stop = 1000001.0
step = 1

    def randrange(self, start, stop=None, step=_ONE):
        """Choose a random item from range(stop) or range(start, stop[, step]).

        Roughly equivalent to ``choice(range(start, stop, step))``
        but supports arbitrarily large ranges and is optimized
        for common cases.

        """

        # This code is a bit messy to make it fast for the
        # common case while still doing adequate error checking.
        istart = _index(start)
        if stop is None:
            # We don't check for "step != 1" because it hasn't been
            # type checked and converted to an integer yet.
            if step is not _ONE:
                raise TypeError('Missing a non-None stop argument')
            if istart > 0:
                return self._randbelow(istart)
            raise ValueError("empty range for randrange()")

        # Stop argument supplied.
>       istop = _index(stop)
E       TypeError: 'float' object cannot be interpreted as an integer

/usr/lib64/python3.11/random.py:306: TypeError
=========================== short test summary info ============================
FAILED tests/test_commandline.py::test_main[False] - TypeError: 'float' objec...
FAILED tests/test_commandline.py::test_main[True] - TypeError: 'float' object...
FAILED tests/test_commandline.py::test_args[False] - TypeError: 'float' objec...
FAILED tests/test_commandline.py::test_args[True] - TypeError: 'float' object...
FAILED tests/test_commandline.py::test_ascii[False] - TypeError: 'float' obje...
FAILED tests/test_commandline.py::test_binary[False] - TypeError: 'float' obj...
=================== 6 failed, 94 passed, 4 skipped in 3.97s ====================

I do not know yet what change in Python caused this.

And I was not able to reproduce this outside of Fedora with plain tox -e py311 because released Cython is currently not compatible with 3.11.0a4, so NumPy installation fails.

However the random.randint(0, 1e6) call in isolation gets it:

$ python3.10
>>> import random
>>> random.randint(0, 1e6)
516518

$ python3.11
>>> import random
>>> random.randint(0, 1e6)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python3.11/random.py", line 330, in randint
    return self.randrange(a, b+1)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.11/random.py", line 306, in randrange
    istop = _index(stop)
            ^^^^^^^^^^^^
TypeError: 'float' object cannot be interpreted as an integer
hroncok commented 2 years ago

I'm trying to figure out what exactly changed in Python. But I guess using random.randint(0, int(1e6)) would be more explicit anyway.

hroncok commented 2 years ago

https://bugs.python.org/issue42222 https://github.com/python/cpython/commit/5afa0a411243210a30526c7459a0ccff5cb88494

randrange: Remove deprecated support for non-integer values

wolph commented 2 years ago

Thank you for chasing the bug!

I guess I'll replace it with 1_000_000 in that case, might be clearer anyhow.

hroncok commented 2 years ago

The randint/randrange change in Python violates Python's own policy for incompatible changes. I've asked the maintainer to revert it: https://bugs.python.org/issue46624

Nevertheless, using 1_000_000 is indeed better.

hroncok commented 2 years ago

Thanks!