Контринтуитивное поведение int () в Python

83

Это четко указано в документации , что Int (число) является преобразование типов напольных покрытий:

int(1.23)
1

а int (string) возвращает int тогда и только тогда, когда строка является целочисленным литералом.

int('1.23')
ValueError

int('1')
1

Есть ли для этого особая причина? Мне кажется нелогичным, что в одном случае функция перекрывается, а в другом - нет.

Стефанс
источник

Ответы:

123

Там нет специальной причины. Python просто применяет свой общий принцип отказа от неявных преобразований, которые являются хорошо известными причинами проблем, особенно для новичков, в таких языках, как Perl и Javascript.

int(some_string)явный запрос на преобразование строки в целочисленный формат; правила для этого преобразования определяют, что строка должна содержать допустимое целочисленное буквальное представление. int(float)явный запрос на преобразование числа с плавающей запятой в целое число; правила этого преобразования определяют, что дробная часть числа с плавающей запятой будет усечена.

Для int("3.1459")возврата 3интерпретатор должен неявно преобразовать строку в число с плавающей запятой. Поскольку Python не поддерживает неявные преобразования, вместо этого он предпочитает вызывать исключение.

Holdenweb
источник
type(3)возвращается <type int>. Однако питон на это не жалуется float("3"). Разве python не преобразует неявно строку в int, а затем в float?
franksands
Нет. «3» является допустимым значением с плавающей запятой, даже если как программный литерал он интерпретируется как целое число. Целочисленное преобразование не требуется.
holdenweb
75

Это почти наверняка случай применения трех принципов из дзен Python :

Явный лучше неявный.

[...] практичность превосходит чистоту

Ошибки никогда не должны проходить тихо

Некоторый процент времени кто-то int('1.23')вызывает неправильное преобразование для своего варианта использования и хочет что-то вроде floatили decimal.Decimalвместо этого. В таких случаях для них явно лучше получить немедленную ошибку, которую они могут исправить, чем молча давать неправильное значение.

В случае, если вы действительно хотите срезанный , что к междунару, это тривиально явно сделать это путем пропускания ее через floatпервую, а затем вызвать одну из int, round, trunc, floorили ceilв зависимости от обстоятельств. Это также делает ваш код более самодокументируемым, защищая от более поздней модификации «корректирующей» гипотетический беззвучно-усечение intвызова float, сделав его ясно , что округленное значением является то , что вы хотите.

lvc
источник
Я думаю, что эти принципы были приняты задолго до того, как был сформулирован дзэн, но в любом случае кажется, что они находятся в гармонии.
holdenweb
17

Иногда может быть полезен мысленный эксперимент.

  • Поведение A: int('1.23')завершается ошибкой. Это существующее поведение.
  • Поведение B: int('1.23')производит 1без ошибок. Это то, что вы предлагаете.

С поведением A просто и тривиально получить эффект поведения B: int(float('1.23'))вместо этого используйте .

С другой стороны, с поведением B получить эффект поведения A значительно сложнее:

def parse_pure_int(s):
    if "." in s:
        raise ValueError("invalid literal for integer with base 10: " + s)
    return int(s)

(и даже с помощью приведенного выше кода у меня нет полной уверенности в том, что не существует каких-то угловых случаев, с которыми он неправильно справляется.)

Таким образом, поведение A более выразительно, чем поведение B.

Еще одна вещь, которую следует учитывать: '1.23'это строковое представление значения с плавающей запятой. Преобразование '1.23'в целое число , концептуально включает в себя два преобразования (строку всплывать на целое число), но int(1.23)и int('1')каждый включает в себя только одну конверсии.


Редактировать:

И действительно, есть угловые случаи, которые приведенный выше код не обрабатывает: 1e-2и 1E-2оба они являются значениями с плавающей запятой.

Jamesdlin
источник
Чтобы уточнить: я бы не предлагал поведение B, потому что это просто опасно, как вы и другие заявили. Я не уверен, что существует лучшее решение, чем текущее. Один из вариантов - дать функциям разные имена, но это просто больше, чем нужно напечатать. Очевидное решение, когда int (1.23) терпит неудачу и только int (float-with-no-decimal-разряды) возвращают целое число, не имеет смысла в динамически типизированном языке.
StefanS
1
Угловой корпус может быть int('123E-2')или int('1L').
Джаред Гогуэн,
11

Проще говоря - это разные функции.

  • int (десятичный) ведет себя как 'пол, т.е. отбросить десятичную часть и вернуть как int'
  • int (строка) ведет себя как «этот текст описывает целое число, преобразовать его и вернуть как int».

Это две разные функции с одинаковым именем, которые возвращают целое число, но это разные функции.

«int» короткое и легко запоминающееся, и его значение применительно к каждому типу интуитивно понятно большинству программистов, поэтому они выбрали его.

Нет никакого смысла в том, что они предоставляют одинаковые или комбинированные функции, они просто имеют одинаковое имя и возвращают один и тот же тип. Их так же легко можно было бы назвать floorDecimalAsInt и convertStringToInt, но они выбрали int, потому что их легко запомнить, (99%) интуитивно понятны, и путаница возникнет редко.

Анализ текста как целого числа для текста, который включает десятичную точку, такую ​​как «4.5», вызовет ошибку на большинстве компьютерных языков и, как ожидается, вызовет ошибку у большинства программистов, поскольку текстовое значение не представляет собой целое число и подразумевает они предоставляют ошибочные данные


источник
2
Тогда почему две «разные функции» имеют одно и то же имя? Звучит как нарушение какой-то дзен-чепухи.
hobbs
потому что название имеет смысл для двух разных функций и является кратким. Int-ify a decimal (floor), преобразовать строку в int (преобразование)
Технически это может помочь вспомнить, что intэто тип (и притом встроенный). Его создатель ( __new__) принимает несколько возможных типов аргументов. Его поведение для каждого типа четко определено.
holdenweb
Этот ответ просто неверен, как указано. intэто на самом деле не функция , а тип, которого __new__и __init__методы взять строку или поплавка аргумент, имея дело с каждым соответствующим образом . Правильнее было бы сказать, что тип обрабатывает два типа аргументов по-разному, но есть только один int.
holdenweb 05