Nieintuicyjne zachowanie int () w Pythonie

83

To wyraźnie stwierdzono w docs że int (liczba) jest konwersja typu podłóg:

int(1.23)
1

a int (string) zwraca wartość int wtedy i tylko wtedy, gdy łańcuch jest literałem liczby całkowitej.

int('1.23')
ValueError

int('1')
1

Czy jest jakiś szczególny powód? Wydaje mi się sprzeczne z intuicją, że funkcja w jednym przypadku ma znaczenie, ale nie w drugim.

StefanS
źródło

Odpowiedzi:

123

Nie ma specjalnego powodu. Python po prostu stosuje swoją ogólną zasadę niewykonania niejawnych konwersji, które są dobrze znanymi przyczynami problemów, szczególnie dla nowicjuszy, w językach takich jak Perl i Javascript.

int(some_string)jest jawnym żądaniem konwersji łańcucha na format całkowity; reguły tej konwersji określają, że łańcuch musi zawierać prawidłową reprezentację literału w postaci liczby całkowitej. int(float)jest jawnym żądaniem konwersji liczby zmiennoprzecinkowej na liczbę całkowitą; reguły tej konwersji określają, że część ułamkowa zmiennej zostanie obcięta.

Aby int("3.1459")zwrócić, 3interpreter musiałby niejawnie przekonwertować napis na zmiennoprzecinkowy. Ponieważ Python nie obsługuje niejawnych konwersji, zamiast tego wybiera zgłoszenie wyjątku.

holdenweb
źródło
type(3)zwraca <type int>. Jednak Python nie narzeka float("3"). Czy Python nie konwertuje niejawnie ciągu na int, a następnie na float?
franksands
Nie. „3” jest poprawną wartością zmiennoprzecinkową, mimo że jako literał programu zostałaby zinterpretowana jako liczba całkowita. Nie jest wymagana żadna konwersja liczb całkowitych.
holdenweb
75

Jest to prawie na pewno przypadek zastosowania trzech zasad z Zen of Python :

Jawne jest lepiej niejawne.

[...] praktyczność przewyższa czystość

Błędy nigdy nie powinny przejść bezgłośnie

int('1.23')Przez pewien procent czasu ktoś wywołuje niewłaściwą konwersję dla swojego przypadku użycia i chce czegoś takiego jak floatlub decimal.Decimalzamiast tego. W takich przypadkach zdecydowanie lepiej jest, jeśli otrzymają natychmiastowy błąd, który mogą naprawić, zamiast po cichu podawać niewłaściwą wartość.

W przypadku, że nie chce obciąć że do int, to jest trywialne jawnie zrobić poprzez przepuszczenie go przez floatpierwszy, a następnie wywołanie jednej z int, round, trunc, floorlub ceilw stosownych przypadkach. To również sprawia, że ​​twój kod jest bardziej samodokumentujący, chroniąc przed późniejszą modyfikacją „poprawiającą” hipotetyczne intwywołanie cichego obcięcia floatprzez wyjaśnienie, że zaokrąglona wartość jest tym, czego chcesz.

lvc
źródło
Myślę, że te zasady zostały przyjęte na długo przed sformułowaniem Zen, ale tak czy inaczej, te dwie rzeczy wydają się być w harmonii.
holdenweb
17

Czasami może się przydać eksperyment myślowy.

  • Zachowanie A: int('1.23')kończy się niepowodzeniem z błędem. To jest istniejące zachowanie.
  • Zachowanie B: int('1.23')produkuje 1bez błędów. Oto, co proponujesz.

W przypadku zachowania A uzyskanie efektu zachowania B jest proste i trywialne: użyj int(float('1.23'))zamiast tego.

Z drugiej strony, w przypadku zachowania B uzyskanie efektu zachowania A jest znacznie bardziej skomplikowane:

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

(a nawet z powyższym kodem nie mam całkowitej pewności, że nie ma jakiegoś narożnika, z którym źle sobie radzi).

Zachowanie A jest zatem bardziej wyraziste niż zachowanie B.

Kolejna rzecz do rozważenia: '1.23'to łańcuchowa reprezentacja wartości zmiennoprzecinkowej. Konwersja '1.23'do całkowitej koncepcyjnie obejmuje dwie konwersje (ciąg pływaka do liczby całkowitej), a int(1.23)i int('1')każda konwersja obejmować tylko jeden.


Edytować:

I rzeczywiście, istnieją przypadki narożne, których powyższy kod nie poradziłby: 1e-2i 1E-2obie są również wartościami zmiennoprzecinkowymi.

jamesdlin
źródło
Dla wyjaśnienia: nie proponowałbym zachowania B, ponieważ jest to po prostu niebezpieczne, jak stwierdziliście ty i inni. Nie jestem pewien, czy istnieje lepsze rozwiązanie niż obecne. Jedną z opcji byłoby nadanie funkcjom różnych nazw, ale to po prostu więcej rzeczy do wpisania. Oczywiste rozwiązanie polegające na tym, że int (1.23) kończy się niepowodzeniem i tylko int (float-with-no-decimal-places) zwraca liczbę całkowitą nie ma sensu w języku dynamicznie typowanym.
StefanS
1
Skrzynka narożna może być int('123E-2')lub int('1L').
Jared Goguen
11

Krótko mówiąc - to nie ta sama funkcja.

  • int (decimal) zachowuje się jak „floor, czyli odrzuć część dziesiętną i wróć jako int”
  • int (string) zachowuje się jak „ten tekst opisuje liczbę całkowitą, konwertuje ją i zwraca jako int”.

Są to 2 różne funkcje o tej samej nazwie, które zwracają liczbę całkowitą, ale są to różne funkcje.

„int” jest krótkie i łatwe do zapamiętania, a jego znaczenie przypisane do każdego typu jest intuicyjne dla większości programistów, dlatego właśnie go wybrali.

Nie ma żadnej sugestii, że zapewniają tę samą lub połączoną funkcjonalność, po prostu mają tę samą nazwę i zwracają ten sam typ. Równie łatwo można je nazwać „floorDecimalAsInt” i „convertStringToInt”, ale wybrali „int”, ponieważ jest łatwy do zapamiętania, (99%) intuicyjny i rzadko zdarzał się błąd.

Przetwarzanie tekstu jako liczby całkowitej dla tekstu zawierającego kropkę dziesiętną, taką jak „4,5”, spowodowałoby błąd w większości języków komputerowych i większość programistów oczekuje, że spowoduje to błąd , ponieważ wartość tekstowa nie reprezentuje liczby całkowitej i implikuje podają błędne dane


źródło
2
Dlaczego więc dwie „różne funkcje” mają tę samą nazwę? Brzmi jak pogwałcenie jakiegoś nonsensu zen.
hobbs
ponieważ nazwa ma sens dla dwóch różnych funkcji i jest zwięzła. Int-ify a decimal (floor), konwertuj ciąg na int (konwersja)
Technicznie może pomóc pamiętać, że intjest to typ (i do tego wbudowany). Jego twórca ( __new__) przyjmuje kilka możliwych typów argumentów. Jego zachowanie dla każdego typu jest dobrze zdefiniowane.
holdenweb
Ta odpowiedź jest po prostu błędna, jak stwierdzono. intw rzeczywistości nie jest funkcją, ale typem, którego metody __new__i __init__metody pobierają argumenty typu string lub float, obsługując odpowiednio każdy z nich. Dokładniej byłoby powiedzieć, że typ przetwarza dwa typy argumentów inaczej, ale jest tylko jeden int.
holdenweb