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.


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.


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.


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'

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\

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.


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.


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ć:

'\\'

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.


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.


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.


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


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 ...).

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.