Dlaczego Python 3 dopuszcza „00” jako dosłowne dla 0, ale nie dopuszcza „01” jako dosłownego dla 1?

111

Dlaczego Python 3 dopuszcza „00” jako literał dla 0, ale nie dopuszcza „01” jako dosłownego dla 1? Czy jest dobry powód? Ta niekonsekwencja wprawia mnie w zakłopotanie. (I mówimy o Pythonie 3, który celowo złamał kompatybilność wsteczną, aby osiągnąć cele takie jak spójność).

Na przykład:

>>> from datetime import time
>>> time(16, 00)
datetime.time(16, 0)
>>> time(16, 01)
  File "<stdin>", line 1
    time(16, 01)
              ^
SyntaxError: invalid token
>>>
mors
źródło
42
Nie można go teraz usunąć, bo złamie wsteczną zgodność z tym pytaniem!
John La Rooy,

Odpowiedzi:

103

Na https://docs.python.org/3/reference/lexical_analysis.html#integer-literals :

Literały całkowite są opisane za pomocą następujących definicji leksykalnych:

integer        ::=  decimalinteger | octinteger | hexinteger | bininteger
decimalinteger ::=  nonzerodigit digit* | "0"+
nonzerodigit   ::=  "1"..."9"
digit          ::=  "0"..."9"
octinteger     ::=  "0" ("o" | "O") octdigit+
hexinteger     ::=  "0" ("x" | "X") hexdigit+
bininteger     ::=  "0" ("b" | "B") bindigit+
octdigit       ::=  "0"..."7"
hexdigit       ::=  digit | "a"..."f" | "A"..."F"
bindigit       ::=  "0" | "1"

Nie ma ograniczeń co do długości literałów całkowitych poza tym, co można przechowywać w dostępnej pamięci.

Należy pamiętać, że zera wiodące w niezerowej liczbie dziesiętnej są niedozwolone. Służy to do ujednoznacznienia literałów ósemkowych w stylu C, których Python używał przed wersją 3.0.

Jak zauważono tutaj, zera wiodące w niezerowej liczbie dziesiętnej są niedozwolone. "0"+jest legalny jako bardzo szczególny przypadek, którego nie było w Pythonie 2 :

integer        ::=  decimalinteger | octinteger | hexinteger | bininteger
decimalinteger ::=  nonzerodigit digit* | "0"
octinteger     ::=  "0" ("o" | "O") octdigit+ | "0" octdigit+

Zatwierdzenie SVN r55866 zaimplementowało PEP 3127 w tokenizerze, który zabrania starych 0<octal>numerów. Jednak, co ciekawe, dodaje również tę notatkę:

/* in any case, allow '0' as a literal */

ze specjalną nonzeroflagą, która rzuca a tylko SyntaxErrorwtedy, gdy następująca sekwencja cyfr zawiera cyfrę niezerową.

To dziwne, ponieważ PEP 3127 nie zezwala na ten przypadek:

Ten dokument PEP proponuje, że możliwość określenia liczby ósemkowej za pomocą zera wiodącego zostanie usunięta z języka w Pythonie 3.0 (i trybie podglądu Pythona 3.0 w wersji 2.6), i że SyntaxError zostanie zgłoszony, gdy początkowe „0” zostanie zaraz po niej następuje kolejna cyfra .

(podkreślenie moje)

Tak więc fakt, że dozwolone jest wielokrotne zerowanie, technicznie narusza PEP i został w zasadzie zaimplementowany jako przypadek specjalny przez Georga Brandla. Dokonał odpowiedniej zmiany w dokumentacji, aby zauważyć, że "0"+jest to uzasadnionedecimalinteger (poprzednio był objęty zakresem octinteger).

Prawdopodobnie nigdy nie dowiemy się dokładnie, dlaczego Georg zdecydował się zrobić"0"+ poprawnym - może to na zawsze pozostać dziwnym przypadkiem narożnym w Pythonie.


AKTUALIZACJA [28 lipca 2015 r.]: To pytanie doprowadziło do ożywionej dyskusji na temat pomysłów na Pythona, do której dołączył Georg :

Steven D'Aprano napisał:

Dlaczego zostało to tak zdefiniowane? […] Dlaczego mielibyśmy pisać 0000, żeby uzyskać zero?

Mógłbym ci powiedzieć, ale wtedy musiałbym cię zabić.

Georg

Później w wątku pojawił się ten raport o błędzie, mający na celu pozbycie się tego specjalnego przypadku. Tutaj, mówi Georg :

Nie przypominam sobie powodu tej celowej zmiany (jak widać ze zmiany dokumentów).

Nie mogę teraz wymyślić dobrego powodu dla tej zmiany [...]

i tak to mamy: dokładna przyczyna tej niespójności jest zagubiona w czasie.

Na koniec zwróć uwagę, że raport o błędzie został odrzucony: wiodące zera będą nadal akceptowane tylko w przypadku zerowych liczb całkowitych w pozostałej części Pythona 3.x.

nneonneo
źródło
6
Dlaczego mówisz „Prawdopodobnie nigdy nie dowiemy się dokładnie, dlaczego Georg zdecydował się…”? Jeśli ktoś, kto go zna, zobaczy ten wątek i poinformuje go o tym, może przyjść i udzielić odpowiedzi! (chyba że wiesz, że na zawsze odmawia omawiania swojej wcześniejszej pracy w Pythonie lub podobnych okoliczności)
mors
1
Nie rozumiem, dlaczego nie stworzyli po prostu drugiego octintegerprzypadku Pythona 2 "0" octdigit*. 0jest literałem ósemkowym w C / C ++.
Random832
1
Właściwie angielski jest pod tym względem nieco niejednoznaczny. Słowo „inny” może znaczyć „jeszcze jeden” lub „inny”. Jedna z poprawnych angielskich interpretacji pogrubionego cytatu z PEP 3127 ma oznaczać, że „Błąd składni zostanie podniesiony, gdy po początkowym„ 0 ”bezpośrednio następuje cyfra inna niż„ 0 ”. chociaż wydaje się, że ta interpretacja jest wspierana przez rzeczywisty kod), ale w każdym razie nie sądzę, aby słuszne było stwierdzenie, że PEP jest technicznie naruszony bez dodatkowego wyjaśnienia tego zdania.
GrandOpener
2
@GrandOpener: Zauważ, że 001jest to nielegalne, podczas gdy Twoja interpretacja uczyniłaby to legalnym (ponieważ znaczenie „natychmiast” powinno być dość jednoznaczne).
nneonneo
Słuszna uwaga. Więc PEP jest zdecydowanie naruszony; niejednoznaczna jest dokładna natura, w jakiej jest naruszana. :)
GrandOpener
17

To jest szczególny przypadek ( "0"+)

2.4.4. Literały całkowite

Literały całkowite są opisane za pomocą następujących definicji leksykalnych:

integer :: = decimalinteger | octinteger | hexinteger | bininteger
decimalinteger :: = cyfra niezerodigitowa * | „0” +
nonzerodigit :: = "1" ... "9"
digit :: = "0" ... "9"
octinteger :: = "0" ("o" | "O") octdigit +
hexinteger :: = "0" ("x" | "X") hexdigit +
bininteger :: = "0" ("b" | "B") bindigit +
octdigit :: = "0" ... "7"
hexdigit :: = digit | "a" ... "f" | „A” ... „F”
bindigit :: = "0" | „1”

Jeśli spojrzysz na gramatykę, łatwo zauważysz, że 0wymaga specjalnego przypadku. Nie wiem jednak, dlaczego „ +” jest tam uważane za konieczne. Czas przejrzeć listę mailingową deweloperów ...


Warto zauważyć, że w Pythonie2 więcej niż jeden 0został przeanalizowany jako an octinteger(wynik końcowy jest 0jednak nadal )

decimalinteger :: = cyfra niezerodigitowa * | „0”
octinteger :: = "0" ("o" | "O") octdigit + | „0” octdigit +
John La Rooy
źródło
1
I jakikolwiek pomysł, dlaczego jest "0"+i nie ma "0"?
lejlot,
1
@lejlot, jeszcze nie - ale jestem zaintrygowany. Jest to jednak zdecydowanie część specyfikacji
John La Rooy,
3

Python2 użył wiodącego zera do określenia liczb ósemkowych:

>>> 010
8

Aby uniknąć tego (? Mylące) zachowanie, Python3 wymaga wyraźnej prefiksy 0b, 0o, 0x:

>>> 0o10
8
dlask
źródło
15
Pozostaje pytanie: dlaczego jest to 00dozwolone? (I 000, 0000itp.)
Michael Geary,
4
@MichaelGeary: prawdopodobnie dlatego, że nie może być niejednoznaczny (00000000 to 0 niezależnie od bazy), a usunięcie go niepotrzebnie złamałoby kod? Wciąż dziwne.
RemcoGerlich,
5
@RemcoGerlich Jeśli się nie mylę, 01to też 1niezależnie od bazy.
Holt,
2
@Holt: ale zezwalasz na „0” + „1”? jako szczególny przypadek prawdopodobnie byłby jeszcze bardziej zagmatwany.
RemcoGerlich,
4
@RemcoGerlich Nigdy nie powiedziałem, że nie;) Mówiłem tylko, że can't be ambiguousnie jest argumentem, ponieważ też 01nie może być niejednoznaczny. IMO, 00sprawa to tylko szczególny przypadek, ponieważ tak 0nie powinno być.
Holt,