Comportamiento contrario a la intuición de int () en Python

83

Se indica claramente en los documentos que int (número) es una conversión de tipo de suelos:

int(1.23)
1

e int (cadena) devuelve un int si y solo si la cadena es un literal entero.

int('1.23')
ValueError

int('1')
1

¿Hay alguna razón especial para eso? Me parece contradictorio que la función se limite en un caso, pero no en el otro.

StefanS
fuente

Respuestas:

123

No hay ninguna razón especial . Python simplemente está aplicando su principio general de no realizar conversiones implícitas, que son causas bien conocidas de problemas, particularmente para los recién llegados, en lenguajes como Perl y Javascript.

int(some_string)es una solicitud explícita para convertir una cadena a formato entero; las reglas para esta conversión especifican que la cadena debe contener una representación literal de entero válida. int(float)es una solicitud explícita para convertir un flotante en un número entero; las reglas para esta conversión especifican que la porción fraccionaria del flotante se truncará.

Para int("3.1459")poder regresar, 3el intérprete tendría que convertir implícitamente la cadena en un flotante. Dado que Python no admite conversiones implícitas, elige generar una excepción en su lugar.

holdenweb
fuente
type(3)devuelve <type int>. Sin embargo, Python no se queja float("3"). ¿Python no está convirtiendo implícitamente la cadena en un int y luego en un flotante?
franksands
No. "3" es un valor de punto flotante válido aunque, como literal de programa, se interpretaría como un número entero. No es necesaria la conversión de números enteros.
holdenweb
75

Es casi seguro que se trata de aplicar tres de los principios del Zen de Python :

Explícito es mejor implícito.

[...] practicidad vence pureza

Los errores nunca deben pasar en silencio

Un porcentaje de las veces, alguien int('1.23')está llamando a la conversión incorrecta para su caso de uso y quiere algo como floato en su decimal.Decimallugar. En estos casos, es claramente mejor para ellos obtener un error inmediato que puedan corregir, en lugar de dar silenciosamente el valor incorrecto.

En el caso de que usted no desea truncar que a un int, es trivial para hacer explícitamente pasándola a través de floatprimero, y luego llamando a uno de int, round, trunc, flooro ceilsegún el caso. Esto también hace que su código sea más autodocumentado, evitando que una modificación posterior "corrija" una intllamada hipotética truncada silenciosamente al floatdejar en claro que el valor redondeado es lo que desea.

lvc
fuente
Creo que esos principios se adoptaron mucho antes de que se formulara el Zen, pero de cualquier manera, los dos parecerían estar en armonía.
holdenweb
17

A veces, un experimento mental puede resultar útil.

  • Comportamiento A: int('1.23')falla con un error. Este es el comportamiento existente.
  • Comportamiento B: int('1.23')produce 1sin error. Esto es lo que propones.

Con el comportamiento A, es sencillo y trivial obtener el efecto del comportamiento B: utilícelo int(float('1.23'))en su lugar.

Por otro lado, con el comportamiento B, obtener el efecto del comportamiento A es significativamente más complicado:

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

(e incluso con el código anterior, no tengo total confianza en que no hay un caso de esquina que se maneje mal).

Por tanto, el comportamiento A es más expresivo que el comportamiento B.

Otra cosa a considerar: '1.23'es una representación de cadena de un valor de punto flotante. Conversión '1.23'a un número entero conceptualmente implica dos conversiones (cadena a flotante a un entero), pero int(1.23)y int('1')cada implican sólo una conversión.


Editar:

Y de hecho, hay casos extremos que el código anterior no manejaría: 1e-2y 1E-2ambos son valores de punto flotante también.

jamesdlin
fuente
Para aclarar: no propondría la conducta B, porque eso es peligroso, como usted y otros dijeron. No estoy seguro de que exista una solución mejor que la actual. Una opción sería dar nombres diferentes a las funciones, pero eso es solo más cosas para escribir. La solución obvia de que int (1.23) falle y solo int (float-with-no-decimal-places) devuelva un número entero no tiene sentido en un lenguaje escrito dinámicamente.
StefanS
1
El caso de la esquina podría ser int('123E-2')o int('1L').
Jared Goguen
11

En palabras simples, no son la misma función.

  • int (decimal) se comporta como 'piso, es decir, elimina la parte decimal y regresa como int'
  • int (cadena) se comporta como 'este texto describe un número entero, conviértalo y devuelve como int'.

Son 2 funciones diferentes con el mismo nombre que devuelven un número entero pero son funciones diferentes.

'int' es corto y fácil de recordar y su significado aplicado a cada tipo es intuitivo para la mayoría de los programadores, por eso lo eligieron.

No hay ninguna implicación de que estén proporcionando la misma funcionalidad o una combinación de funciones, simplemente tienen el mismo nombre y devuelven el mismo tipo. Podrían llamarse fácilmente 'floorDecimalAsInt' y 'convertStringToInt', pero eligieron 'int' porque es fácil de recordar, (99%) intuitivo y la confusión rara vez ocurre.

Analizar el texto como un número entero para el texto que incluye un punto decimal como "4.5" arrojaría un error en la mayoría de los lenguajes de computadora y se esperaría que arrojara un error para la mayoría de los programadores, ya que el valor de texto no representa un número entero e implica están proporcionando datos erróneos


fuente
2
Entonces, ¿por qué dos 'funciones diferentes' tienen el mismo nombre? Suena como una violación de una tontería zen.
hobbs
porque el nombre tiene sentido para 2 funciones diferentes y es sucinto. Int-ify un decimal (piso), convierta una cadena en un int (conversión)
Técnicamente, podría ayudar recordar que intes un tipo (y uno incorporado). Su creador ( __new__) toma varios tipos de argumentos posibles. Su comportamiento para cada tipo está bien definido.
holdenweb
Esta respuesta es simplemente incorrecta como se indica. intde hecho, no es una función sino un tipo, cuyos métodos __new__y __init__toman una cadena o un argumento flotante, tratando cada uno de ellos de manera apropiada. Sería más exacto decir que el tipo procesa los dos tipos de argumentos de manera diferente, pero solo hay uno int.
holdenweb