Разбор int из строки с пробелами отличается от py2 до py3

Разбор строк с пробелами в целые числа изменен с Python2 на Python3.

В Python2 это:

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

тогда как в 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'

Как только я понял это, я попытался найти некоторые объяснения / уточнения этого изменения в документах, но ничего не смог найти.

Итак, мои вопросы: как изменить код для перехода с py2 на py3? Подходит ли i = int(s.replace(' ','')) путь? Или есть лучший совет? И есть ли какое-то описание этого изменения, которое я просто не нашел?


person PVitt    schedule 17.10.2017    source источник
comment
Документы Python 2.7 и 3.6 для int() говорят: «Необязательно, литерал может предшествовать + или - (без пробела между ними) и окружен пробелами. Поэтому я предполагаю, что это случайно работает только в Python 2.7.   -  person Bernhard    schedule 17.10.2017
comment
@Bernhard: документация была исправлена ​​после того, как эта ошибка была исправлена ​​(она была перенесена из 3 только для версии 2.7). Однако ошибка не была перенесена в версию 2.6, поскольку она могла нарушить существующий код.   -  person Martijn Pieters    schedule 17.10.2017


Ответы (3)


Это было явно изменено в Python 3 в ответ на issue 1779:

Я обнаружил, что при преобразовании строки в int или float преобразование int допускает пробелы после знака, а преобразование float — нет. Я думаю, они должны быть последовательными.

Это было отмечено в журнале изменений 3.0a3 (с опечатка в номере выпуска):

  • Проблема №1769: Теперь int("- 1") больше не допускается.

Разрешение пробелов было несовместимо с другими числовыми преобразованиями.

Самый быстрый способ исправить это — использовать str.replace(), да:

>>> 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

Документация по Python 2.7 была обновлена ​​постфактум путем резервного копирования документации по Python 3. Теперь в нем прямо указано, что между знаком и цифрами не должно быть пробелов. Так что официально пробелы больше не поддерживаются, но в интересах сохранения обратной совместимости ошибка осталась.

person Martijn Pieters    schedule 17.10.2017
comment
Этот ответ достаточно охватывает все мои вопросы. Большое спасибо. - person PVitt; 17.10.2017

Кажется, что пробелы в строковых литералах не отбрасываются в Python 3, однако синтаксический анализатор Python по-прежнему игнорирует пробелы, когда они встречаются в числовых литералах:

>>> e = -   11
>>> e
-11

Таким образом, вы можете использовать ast.literal_eval непосредственно во входной строке как в Python 2, так и в 3, поэтому пробелы не учитываются:

>>> import ast
>>> ast.literal_eval('-      11 ')
-11
person Moses Koledoye    schedule 17.10.2017
comment
Это... очень медленно. - person Martijn Pieters; 17.10.2017
comment
Это в 20 раз медленнее, чем при использовании str.replace() и int(). - person Martijn Pieters; 17.10.2017

Не изобретая велосипед. Для ПГ2 и ПГ3

import re
int(re.sub(r'[^\d\-]', '', '- 11'))

Тесты

>>> int(re.sub(r'[^\d\.\-]', '', '- 11'))
-11
>>> int(re.sub(r'[^\d\.\-]', '', '+ 11'))
11
>>> int(re.sub(r'[^\d\.\-]', '', '+ 11easd'))
11
>>> int(re.sub(r'[^\d\.\-]', '', '+ 11easd3325'))
113325
person Kamo Petrosyan    schedule 17.10.2017
comment
int() не допускает произвольных букв; если вы хотите преобразовать ввод допустимой строки, но хотите обрабатывать пробелы между знаком и цифрами как допустимые, это неправильный подход. - person Martijn Pieters; 17.10.2017
comment
Это также половина скорости str.replace(). - person Martijn Pieters; 17.10.2017