Search code examples
pythonstringintpython-2to3

Parsing int from string with whitespace differs from py2 to py3


Parsing strings with whitespace to integers changed from Python2 to Python3.

In Python2 it is:

>>> int('-11')
-11
>>> int('- 11')
-11

whereas in Python3:

>>> int('-11')
-11
>>> int('- 11')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '- 11'

Once I figured this out, I tried to find some explanations for/elaborations on this change in the docs, but couldn't find anything.

So my questions are: How to modify code to migrate from py2 to py3? Is i = int(s.replace(' ','')) the way to go? Or is there better advice? And is there some description of that change I just didn't find?


Solution

  • This was changed explicitly in Python 3 in response to issue 1779:

    I discovered that when converting a string to an int or float, the int conversion allows whitespace after the sign, while the float conversion doesn't. I think they should be consistent.

    This was noted in the 3.0a3 changelog (with a typo in the issue number):

    • Issue #1769: Now int("- 1") is not allowed any more.

    Allowing spaces there was inconsistent with other numeric conversions.

    The fastest way to fix this is to use str.replace(), yes:

    >>> import timeit
    >>> timeit.timeit('int("- 1".replace(" ", ""))')
    0.37510599600500427
    >>> timeit.timeit('int("- 1".translate(map))', 'map = {32: None}')
    0.45536769900354557
    >>> timeit.timeit('literal_eval("- 1")', 'from ast import literal_eval')
    6.255796805999125
    >>> timeit.timeit('int(extract("- 1"))', 'import re; from functools import partial; extract = partial(re.compile(r"[^\d\.\-]").sub, "")')
    0.7367695900029503
    

    The Python 2.7 documentation was updated after the fact, by backporting the Python 3 documentation. It now states explicitly that there should be no whitespace between sign and digits. So officially, whitespace is no longer supported, but in the interest of not breaking backwards compatibility, the bug is left in.