Comportement contre-intuitif de int () en python

83

Il est clairement indiqué dans la documentation que int (number) est une conversion de type de revêtement de sol:

int(1.23)
1

et int (string) renvoie un int si et seulement si la chaîne est un entier littéral.

int('1.23')
ValueError

int('1')
1

Y a-t-il une raison particulière à cela? Je trouve qu'il est contre-intuitif que la fonction fonctionne dans un cas, mais pas dans l'autre.

StefanS
la source

Réponses:

123

Il n'y a pas de raison particulière . Python applique simplement son principe général de ne pas effectuer de conversions implicites, qui sont des causes bien connues de problèmes, en particulier pour les nouveaux venus, dans des langages tels que Perl et Javascript.

int(some_string)est une demande explicite de conversion d'une chaîne au format entier; les règles de cette conversion spécifient que la chaîne doit contenir une représentation littérale entière valide. int(float)est une demande explicite de conversion d'un flottant en entier; les règles de cette conversion spécifient que la partie fractionnaire du flottant sera tronquée.

Pour int("3.1459")retourner, 3l'interpréteur devrait implicitement convertir la chaîne en un flottant. Étant donné que Python ne prend pas en charge les conversions implicites, il choisit de lever une exception à la place.

holdenweb
la source
type(3)revient <type int>. Cependant, python ne se plaint pas float("3"). Python ne convertit-il pas implicitement la chaîne en un entier, puis en un flottant?
franksands
Non. "3" est une valeur à virgule flottante valide même si en tant que littéral de programme, elle serait interprétée comme un entier. Aucune conversion d'entier n'est nécessaire.
holdenweb
75

C'est presque certainement un cas d'application de trois des principes du Zen de Python :

Explicite est mieux implicite.

[...] la fonctionnalité bat la pureté

Les erreurs ne devraient jamais passer silencieusement

Un certain pourcentage du temps, quelqu'un int('1.23')appelle la mauvaise conversion pour son cas d'utilisation et veut quelque chose comme floatou à la decimal.Decimalplace. Dans ces cas, il est clairement préférable pour eux d'obtenir une erreur immédiate qu'ils peuvent corriger, plutôt que de donner silencieusement la mauvaise valeur.

Dans le cas où vous ne voulez tronquer que pour un entier, il est trivial de faire explicitement en passant à travers d' floatabord, puis d' appeler l' un des int, round, trunc, floorou ceilselon le cas. Cela rend également votre code plus auto-documenté, en vous protégeant contre une modification ultérieure "corrigeant" un intappel hypothétique tronquant en silence en floatindiquant clairement que la valeur arrondie est ce que vous voulez.

lvc
la source
Je pense que ces principes ont été adoptés bien avant la formulation du Zen, mais de toute façon, les deux sembleraient être en harmonie.
holdenweb
17

Parfois, une expérience de pensée peut être utile.

  • Comportement A: int('1.23')échoue avec une erreur. C'est le comportement existant.
  • Comportement B: int('1.23')produit 1sans erreur. C'est ce que vous proposez.

Avec le comportement A, il est simple et trivial d'obtenir l'effet du comportement B: utilisez int(float('1.23'))plutôt.

D'un autre côté, avec le comportement B, obtenir l'effet du comportement A est beaucoup plus compliqué:

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

(et même avec le code ci-dessus, je ne suis pas totalement convaincu qu'il n'y a pas de cas d'angle que cela gêne.)

Le comportement A est donc plus expressif que le comportement B.

Une autre chose à considérer: '1.23'est une représentation sous forme de chaîne d'une valeur à virgule flottante. La conversion '1.23'à un nombre entier implique conceptuellement deux conversions (chaîne à flotteur entier), mais int(1.23)et int('1')comprennent chacune une seule conversion.


Éditer:

Et en effet, il y a des cas d'angle que le code ci-dessus ne gérerait pas: 1e-2et 1E-2sont également des valeurs à virgule flottante.

Jamesdlin
la source
Pour clarifier: je ne proposerais pas le comportement B, parce que c'est juste dangereux, comme vous et d'autres l'avez déclaré. Je ne suis pas sûr qu'une meilleure solution que l'actuelle existe même. Une option serait de donner des noms différents aux fonctions, mais c'est juste plus de choses à taper. La solution évidente d'avoir int (1.23) échoue et seulement int (float-sans-décimales) renvoyant un entier n'a aucun sens dans un langage typé dynamiquement.
StefanS
1
Le cas d'angle pourrait être int('123E-2')ou int('1L').
Jared Goguen
11

En termes simples, ce n'est pas la même fonction.

  • int (decimal) se comporte comme 'floor ie supprime la partie décimale et retourne comme int'
  • int (string) se comporte comme 'ce texte décrit un entier, convertissez-le et retournez en int'.

Ce sont 2 fonctions différentes avec le même nom qui renvoient un entier mais ce sont des fonctions différentes.

«int» est court et facile à retenir et sa signification appliquée à chaque type est intuitive pour la plupart des programmeurs, c'est pourquoi ils l'ont choisi.

Il n'y a aucune implication qu'ils fournissent la même fonctionnalité ou des fonctionnalités combinées, ils ont simplement le même nom et renvoient le même type. Ils pourraient aussi facilement être appelés «floorDecimalAsInt» et «convertStringToInt», mais ils ont opté pour «int» car il est facile à retenir, (99%) intuitif et la confusion se produirait rarement.

L'analyse du texte en tant qu'entier pour le texte qui incluait un point décimal tel que «4,5» générerait une erreur dans la majorité des langages informatiques et devrait générer une erreur par la majorité des programmeurs, car la valeur du texte ne représente pas un entier et implique ils fournissent des données erronées


la source
2
Alors pourquoi deux «fonctions différentes» ont-elles le même nom? Sonne comme une violation de certaines absurdités zen.
hobbs
parce que le nom a un sens pour 2 fonctions différentes et est succinct. Int-ify a decimal (floor), convert a string to an int (conversion)
Techniquement, il peut être utile de se rappeler qu'il ints'agit d'un type (et d'un type intégré en plus). Son créateur ( __new__) prend un certain nombre de types d'arguments possibles. Son comportement pour chaque type est bien défini.
holdenweb
Cette réponse est tout simplement fausse comme indiqué. intn'est en fait pas une fonction mais un type, dont les méthodes __new__et __init__prennent une chaîne ou un argument flottant, traitant chacun de manière appropriée. Il serait plus précis de dire que le type traite les deux types d'argument différemment, mais il n'y en a qu'un int.
holdenweb