Dlaczego surowe literały łańcuchowe Pythona nie mogą kończyć się pojedynczym lewym ukośnikiem?

179

Technicznie, dowolna nieparzysta liczba ukośników odwrotnych, zgodnie z opisem w dokumentacji .

>>> r'\'
  File "<stdin>", line 1
    r'\'
       ^
SyntaxError: EOL while scanning string literal
>>> r'\\'
'\\\\'
>>> r'\\\'
  File "<stdin>", line 1
    r'\\\'
         ^
SyntaxError: EOL while scanning string literal

Wygląda na to, że parser może po prostu traktować odwrotne ukośniki w surowych ciągach jako zwykłe znaki (czy nie o to chodzi w surowych ciągach?), Ale prawdopodobnie brakuje mi czegoś oczywistego.

cdleary
źródło
8
wygląda na to, że jest to teraz FAQ . mogło nie być, kiedy zadałeś pytanie. Wiem, że cytowane przez ciebie dokumenty mówią prawie to samo, ale pomyślałem, że dodam inne źródło dokumentacji.
oob

Odpowiedzi:

124

Powód jest wyjaśniony w części tej sekcji, którą zaznaczyłem pogrubioną czcionką:

Cudzysłowy mogą być poprzedzane ukośnikiem odwrotnym, ale ukośnik odwrotny pozostaje w ciągu; na przykład r"\""jest prawidłowym literałem ciągu składającym się z dwóch znaków: ukośnika odwrotnego i podwójnego cudzysłowu; r"\"nie jest prawidłowym literałem ciągu (nawet nieprzetworzony ciąg nie może kończyć się nieparzystą liczbą odwrotnych ukośników). W szczególności, nieprzetworzony ciąg nie może kończyć się pojedynczym ukośnikiem odwrotnym (ponieważ ukośnik odwrotny byłby usuwany z następującego cudzysłowu). Zauważ również, że pojedynczy lewy ukośnik, po którym następuje znak nowej linii, jest interpretowany jako te dwa znaki jako część ciągu, a nie jako kontynuacja wiersza.

Tak więc nieprzetworzone łańcuchy nie są w 100% surowe, nadal istnieje pewne podstawowe przetwarzanie odwrotnego ukośnika.

oefe
źródło
21
Och wow ... to dziwne. Dobry chwyt. Ma sens, że r '\' '== "\\'", ale wciąż jest dziwne, że znak ucieczki ma efekt bez znikania.
cdleary
2
@ihightower może to działać w przypadku ścieżek systemu plików, ale są inne zastosowania ukośnika odwrotnego. A w przypadku ścieżek systemu plików nie koduj separatora na stałe. Użyj 'os.path.sep' lub lepiej funkcji wyższego poziomu 'os.path'. (Lub „pathlib”, jeśli jest dostępny)
oefe
5
Uwaga: Obejście polega na użyciu sąsiedniej konkatentacji literałów. r"foo\bar\baz" "\\"(zawijaj pareny, jeśli niejednoznaczne) utworzy pojedynczy literał w czasie kompilacji, którego pierwsza część jest surowa, a tylko ostatni mały bit nie jest surowy, aby umożliwić końcowy ukośnik odwrotny.
ShadowRanger
2
IMO to po prostu powtórzyło pytanie (co jest dozwolone / będzie działać, a co nie), bez wyjaśniania, dlaczego zostało to zaprojektowane w ten sposób. Istnieje wpis w FAQ, który w pewnym sensie wyjaśnia dlaczego (nieprzetworzone ciągi znaków zostały zaprojektowane do określonego celu i ma to sens w kontekście tego celu).
ShreevatsaR
3
Jaki jest zatem sens surowych strun? Wydaje się, że jest to podejrzana realizacja koncepcji.
Matthew James Briggs
101

Całe błędne przekonanie na temat nieprzetworzonych ciągów znaków w Pythonie jest takie, że większość ludzi uważa, że ​​ukośnik odwrotny (w ciągu nieprzetworzonym) jest zwykłym znakiem, jak wszystkie inne. Nie jest. Kluczem do zrozumienia jest sekwencja samouczków w języku Python:

Gdy obecny jest przedrostek „ r ” lub „ R ”, znak następujący po ukośniku odwrotnym jest zawarty w ciągu bez zmiany, a wszystkie ukośniki odwrotne pozostają w ciągu

Tak więc każdy znak następujący po odwrotnym ukośniku jest częścią nieprzetworzonego ciągu. Gdy parser wpisze nieprzetworzony ciąg (inny niż Unicode) i napotka ukośnik odwrotny, wie, że są 2 znaki (ukośnik odwrotny i znak po nim).

Tą drogą:

r'abc \ d ' obejmuje a, b, c, \, d

r'abc \ 'd' obejmuje a, b, c, \, ', d

r'abc \ '' obejmuje a, b, c, \, '

i:

r'abc \ ' zawiera a, b, c, \,' ale nie ma teraz kończącego cudzysłowu.

Ostatni przypadek pokazuje, że zgodnie z dokumentacją parser nie może teraz znaleźć cudzysłowu zamykającego, ponieważ ostatni cytat, który widzisz powyżej, jest częścią ciągu, tj. Ukośnik odwrotny nie może być tutaj ostatni, ponieważ pochłonie znak zamykający łańcuch.

Artur
źródło
8
To jest właściwie jaśniejsze niż zaakceptowana odpowiedź. Niezłe załamanie.
Mad Physicist
4
Uważam też to znacznie jaśniejsze niż Zaakceptowanych odpowiedź, a ja też zdarzyć się fizyk
xdavidliu
22

Tak to jest! Widzę to jako jedną z tych małych wad w Pythonie!

Myślę, że nie ma ku temu dobrego powodu, ale na pewno nie jest to analizowanie; bardzo łatwo jest przeanalizować nieprzetworzone łańcuchy z \ jako ostatnim znakiem.

Problem polega na tym, że jeśli pozwolisz, aby \ był ostatnim znakiem w nieprzetworzonym łańcuchu, nie będziesz w stanie umieścić "wewnątrz nieprzetworzonego łańcucha. Wygląda na to, że Python pozwolił" zamiast dopuszczać \ jako ostatni znak.

Nie powinno to jednak powodować żadnych problemów.

Jeśli martwisz się, że nie będziesz w stanie łatwo pisać ścieżek folderów systemu Windows, takich jak c:\mypath\wtedy, nie martw się, ponieważ możesz je przedstawić jako r"C:\mypath", a jeśli chcesz dołączyć nazwę podkatalogu, nie rób tego z konkatenacją ciągów, ponieważ i tak nie jest to właściwy sposób! posługiwać sięos.path.join

>>> import os
>>> os.path.join(r"C:\mypath", "subfolder")
'C:\\mypath\\subfolder'
hasen
źródło
2
Dobry materiał pomocniczy. :-) Jednak adwokat diabła: czasami chcesz odróżnić ścieżki plików od ścieżek katalogów, dodając separator ścieżek. Fajną rzeczą w os.path.join jest to, że je zwinie: assert os.path.join ('/ home / cdleary /', 'foo /', 'bar /') == '/ home / cdleary / foo / bar / '
cdleary
Nie robi to jednak (technicznej) różnicy! os.path.isdir powie Ci, czy dana ścieżka jest katalogiem (folderem)
hasen
2
Tak, to tylko wskazanie komuś czytającemu kod, czy oczekujesz, że ścieżka będzie katalogiem, czy plikiem.
cdleary
Konwencja w systemie Windows jest taka, że ​​pliki zawsze mają rozszerzenie. jest mało prawdopodobne (w normalnych okolicznościach), aby mieć plik tekstowy ze ścieżką taką jak c: \ path \ data
hasen
5
..lub możesz przedstawić je jako „c: / mypath” i całkowicie zapomnieć o swoich problemach z ukośnikiem :-)
John Fouhy
14

Aby zakończyć nieprzetworzony ciąg ukośnikiem, sugeruję skorzystać z tej sztuczki:

>>> print r"c:\test"'\\'
test\
Charles Beattie
źródło
14

Inną sztuczką jest użycie chr (92), który daje „\”.

Niedawno musiałem wyczyścić ciąg odwrotnych ukośników i następujące rozwiązanie załatwiło sprawę:

CleanString = DirtyString.replace(chr(92),'')

Zdaję sobie sprawę, że to nie dba o „dlaczego”, ale wątek przyciąga wiele osób szukających rozwiązania pilnego problemu.

Geekworking
źródło
Ale co, jeśli oryginalny ciąg zawiera odwrotne ukośniki?
Joseph Redfern,
2
chr (92) jest strasznie niejasne, prawdopodobnie lepiej jest użyć "\\"(nie surowy sznurek z backslash)
clemep
9

Ponieważ \ "jest dozwolone wewnątrz nieprzetworzonego ciągu. W takim przypadku nie można go użyć do zidentyfikowania końca literału ciągu.

Dlaczego nie przestać analizować literału ciągu, gdy napotkasz pierwszy „?

W takim przypadku \ "nie byłoby dozwolone wewnątrz literału ciągu. Ale tak jest.

Brian R. Bondy
źródło
1
Dokładnie. Projektanci Pythona prawdopodobnie ocenili prawdopodobieństwo dwóch alternatyw: dwuznakowej sekwencji w \"dowolnym miejscu w ciągu znaków w podwójnych cudzysłowach, LUB \ na końcu nieprzetworzonego ciągu w cudzysłowie. Statystyki użytkowania muszą faworyzować sekwencję dwóch znaków w dowolnym miejscu w porównaniu z sekwencją jednoznakową na końcu.
płyty grzejne
3

Przyczyną r'\'błędu składniowego jest to, że chociaż wyrażenie łańcuchowe jest surowe, użyte cudzysłowy (pojedyncze lub podwójne) zawsze muszą zostać zmienione, ponieważ w przeciwnym razie oznaczałyby koniec cudzysłowu. Więc jeśli chcesz wyrazić pojedynczy cudzysłów w pojedynczym cudzysłowie, nie ma innego sposobu niż użycie \'. To samo dotyczy podwójnych cudzysłowów.

Ale możesz użyć:

'\\'
Gumbo
źródło
4
Nie odpowiada „dlaczego” :-)
cdleary
2

Inny użytkownik, który od tego czasu usunął swoją odpowiedź (nie jest pewien, czy chciałby otrzymać kredyt), zasugerował, że projektanci języka Python mogą być w stanie uprościć projekt parsera, stosując te same reguły analizowania i rozszerzając znaki ucieczki do postaci surowej, jak po namyśle (jeśli literał został oznaczony jako surowy).

Pomyślałem, że to ciekawy pomysł i włączam go jako wiki społeczności dla potomnych.

cdleary
źródło
Ale może pozwolić ci uniknąć dwóch oddzielnych ścieżek kodu ciągu-literału-parsera.
cdleary
2

Pomimo swojej roli, nawet nieprzetworzony ciąg nie może kończyć się pojedynczym ukośnikiem odwrotnym, ponieważ ukośnik odwrotny wymyka się następującemu znakowi cudzysłowu - nadal musisz uciec od otaczającego znaku cudzysłowu, aby osadzić go w ciągu. Oznacza to, że r "... \" nie jest prawidłowym literałem łańcuchowym - nieprzetworzony łańcuch nie może kończyć się nieparzystą liczbą odwrotnych ukośników.
Jeśli chcesz zakończyć nieprzetworzony ciąg pojedynczym lewym ukośnikiem, możesz użyć dwóch i odciąć drugi.

pawandeep singh
źródło
1

Wychodząc z C, jest dla mnie całkiem jasne, że pojedynczy znak \ działa jak znak ucieczki, umożliwiając umieszczanie znaków specjalnych, takich jak nowe linie, tabulatory i cudzysłowy, w łańcuchach.

To faktycznie zabrania \ as ostatniego znaku, ponieważ ucieknie przed "i sprawi, że parser się zakrztusi. Ale jak wskazano wcześniej \ jest legalny.


źródło
1
Tak - sedno problemu polegało na tym, że nieprzetworzone łańcuchy traktują \ jako literał zamiast początku sekwencji ucieczki. Dziwne jest to, że nadal ma właściwości ucieczki dla cytowania, mimo że jest traktowany jako znak dosłowny.
cdleary
1

kilka porad :

1) jeśli musisz manipulować odwrotnym ukośnikiem dla ścieżki, to standardowy moduł Pythona os.path jest twoim przyjacielem. na przykład :

os.path.normpath ('c: / folder1 /')

2) jeśli chcesz zbudować łańcuchy z odwrotnym ukośnikiem, ALE bez odwrotnego ukośnika na końcu swojego ciągu, to surowy łańcuch jest twoim przyjacielem (użyj przedrostka „r” przed literałem). na przykład :

r'\one \two \three'

3) jeśli chcesz poprzedzić ciąg w zmiennej X odwrotnym ukośnikiem, możesz to zrobić:

X='dummy'
bs=r'\ ' # don't forget the space after backslash or you will get EOL error
X2=bs[0]+X  # X2 now contains \dummy

4) jeśli chcesz utworzyć ciąg z ukośnikiem odwrotnym na końcu, połącz końcówki 2 i 3:

voice_name='upper'
lilypond_display=r'\DisplayLilyMusic \ ' # don't forget the space at the end
lilypond_statement=lilypond_display[:-1]+voice_name

zawiera teraz lilypond_statement "\DisplayLilyMusic \upper"

niech żyje Python! :)

n3on


źródło
1
Żaden z nich nie odpowiada na pytanie „dlaczego”, ale nie należy używać punktów 3 i 4. Cięcie i dodawanie ciągów jest ogólnie złą praktyką i powinieneś preferować r '\ dummy' dla # 3 (co działa dobrze) i '' .join ([r '\ DisplayLilyMusic', r '\ upper']) do # 4.
cdleary
1
Powodem jest to, że ciągi są niezmienne, a każdy wycinek / konkatenacja tworzy nowy niezmienny obiekt ciągu, który jest zwykle odrzucany. Lepiej zebrać je wszystkie i połączyć w jednym kroku dzięki str.join (components)
cdleary
Och, ups - źle zrozumiałeś, co masz na myśli dla # 3. Myślę, że preferowane jest proste '\\' + X zamiast tworzenia łańcucha tylko po to, aby go pokroić.
cdleary
Po prostu znajdź os.path.normpath usunie tylny ukośnik odwrotny ... Więc jak mam połączyć nazwę pliku ze ścieżką ...
Jing He
0

Napotkałem ten problem i znalazłem częściowe rozwiązanie, które jest dobre w niektórych przypadkach. Pomimo że Python nie jest w stanie zakończyć łańcucha pojedynczym ukośnikiem odwrotnym, można go serializować i zapisać w pliku tekstowym z pojedynczym ukośnikiem odwrotnym na końcu. Dlatego jeśli potrzebujesz zapisać tekst z pojedynczym ukośnikiem odwrotnym na swoim komputerze, jest możliwe:

x = 'a string\\' 
x
'a string\\' 

# Now save it in a text file and it will appear with a single backslash:

with open("my_file.txt", 'w') as h:
    h.write(x)

BTW, nie działa z json, jeśli zrzucisz go za pomocą biblioteki json w Pythonie.

Wreszcie pracuję ze Spyderem i zauważyłem, że jeśli otworzę zmienną w edytorze tekstu pająka, klikając dwukrotnie jej nazwę w eksploratorze zmiennych, jest ona prezentowana z pojedynczym ukośnikiem odwrotnym i można ją w ten sposób skopiować do schowka (nie jest bardzo pomocny dla większości potrzeb, ale może dla niektórych ...).

BossaNova
źródło