¿Por qué los literales de cadena sin procesar de Python no pueden terminar con una barra diagonal inversa simple?

179

Técnicamente, cualquier número impar de barras invertidas, como se describe en la documentación .

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

Parece que el analizador podría tratar las barras invertidas en cadenas sin formato como caracteres normales (¿no es eso de lo que se tratan las cadenas sin formato?), Pero probablemente me estoy perdiendo algo obvio.

cdleary
fuente
8
Parece que esto es ahora una pregunta frecuente . podría no haber sido cuando hizo la pregunta. Sé que los documentos que citó dicen más o menos lo mismo, pero pensé que agregaría otra fuente de documentación.
oob

Respuestas:

124

La razón se explica en la parte de esa sección que destaqué en negrita:

Las comillas de cadena se pueden escapar con una barra diagonal inversa, pero la barra diagonal inversa permanece en la cadena; por ejemplo, r"\""es un literal de cadena válido que consta de dos caracteres: una barra diagonal inversa y una comilla doble; r"\"no es un literal de cadena válido (incluso una cadena sin procesar no puede terminar en un número impar de barras invertidas). Específicamente, una cadena sin formato no puede terminar en una barra diagonal inversa simple (ya que la barra diagonal inversa escaparía al siguiente carácter de comillas). Tenga en cuenta también que una barra diagonal inversa seguida de una nueva línea se interpreta como esos dos caracteres como parte de la cadena, no como una continuación de línea.

Por lo tanto, las cadenas sin formato no son 100% sin formato, todavía hay un procesamiento rudimentario de barra invertida.

oefe
fuente
21
Oh wow ... eso es raro. Buena atrapada. Tiene sentido que r '\' '== "\\'" pero aún es extraño que el personaje de escape tenga un efecto sin desaparecer.
cdleary
2
@ihightower esto puede funcionar para las rutas del sistema de archivos, pero hay otros usos de la barra invertida. Y para las rutas del sistema de archivos, no codifique el separador. Use 'os.path.sep', o mejor las características de nivel superior de 'os.path'. (O 'pathlib', cuando esté disponible)
oefe
55
Nota: la solución consiste en utilizar la concatenación literal adyacente. r"foo\bar\baz" "\\"(envolver en parens si es ambiguo) creará un único literal en tiempo de compilación, la primera parte de la cual es sin formato, y solo el último pequeño bit no es sin formato, para permitir la barra invertida final.
ShadowRanger
2
En mi opinión, esto solo reafirma la pregunta (qué está permitido / funcionará y qué no), sin decir por qué está diseñado de esta manera. Hay una entrada de preguntas frecuentes que explica el por qué (las cadenas sin formato se diseñaron para un propósito específico, y tiene sentido en el contexto de ese propósito).
ShreevatsaR
3
¿Cuál es el punto de cadenas sin procesar entonces? Parece una implementación sombría del concepto.
Matthew James Briggs
101

Todo el concepto erróneo sobre las cadenas en bruto de Python es que la mayoría de la gente piensa que la barra invertida (dentro de una cadena en bruto) es solo un carácter normal como todos los demás. No lo es. La clave para entender es la secuencia tutorial de esta pitón:

Cuando está presente un prefijo ' r ' o ' R ', un carácter que sigue a una barra diagonal inversa se incluye en la cadena sin cambios, y todas las barras diagonales inversas quedan en la cadena

Por lo tanto, cualquier carácter que siga una barra diagonal inversa es parte de una cadena sin formato. Una vez que el analizador ingresa una cadena sin procesar (no Unicode) y encuentra una barra invertida, sabe que hay 2 caracteres (una barra invertida y un carácter que le sigue).

De esta manera:

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

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

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

y:

r'abc \ ' comprende a, b, c, \,' pero no hay una cita final ahora.

El último caso muestra que, según la documentación, ahora un analizador no puede encontrar la cita de cierre, ya que la última cita que ve arriba es parte de la cadena, es decir, la barra invertida no puede ser la última aquí, ya que 'devorará' la cadena de caracteres de cierre.

Artur
fuente
8
Esto es realmente más claro que la respuesta aceptada. Buen desglose.
Físico loco
44
También encuentro esto significativamente más claro que la respuesta aceptada, y también soy físico
xdavidliu
22

¡Esa es la forma en que está! ¡Lo veo como uno de esos pequeños defectos en Python!

No creo que haya una buena razón para ello, pero definitivamente no está analizando; Es muy fácil analizar cadenas sin procesar con \ como último carácter.

El problema es que si permites que \ sea el último carácter en una cadena sin formato, entonces no podrás poner "dentro de una cadena sin formato. Parece que Python fue con permitir" en lugar de permitir \ como último carácter.

Sin embargo, esto no debería causar ningún problema.

Si le preocupa no poder escribir fácilmente parches de carpetas de Windows, como por ejemplo c:\mypath\, no se preocupe, puede representarlos como r"C:\mypath"y, si necesita agregar un nombre de subdirectorio, no lo haga con concatenación de cadenas, para ¡no es la forma correcta de hacerlo de todos modos! utilizaros.path.join

>>> import os
>>> os.path.join(r"C:\mypath", "subfolder")
'C:\\mypath\\subfolder'
Hasen
fuente
2
Buen material auxiliar. :-) Sin embargo, el defensor del diablo: a veces desea diferenciar las rutas de archivo de las rutas de directorio agregando el separador de ruta. Lo bueno de os.path.join es que los colapsará: afirme os.path.join ('/ home / cdleary /', 'foo /', 'bar /') == '/ home / cdleary / foo / bar / '
cdleary
¡Sin embargo, no hace una diferencia (técnica)! os.path.isdir le indicará si un cierto camino es un directorio (carpeta)
Hasen
2
Sí, es solo para indicarle a alguien que lee el código si espera que una ruta sea un directorio o un archivo.
cdleary
La convención en Windows es que los archivos tienen una extensión, siempre. no es probable (en circunstancias normales) tener un archivo de texto con una ruta como c: \ ruta \ datos
hasen
55
..o puede representarlos como "c: / mypath" y olvidarse por completo de sus problemas de barra invertida :-)
John Fouhy
14

Para que pueda terminar una cadena sin procesar con una barra inclinada, le sugiero que pueda usar este truco:

>>> print r"c:\test"'\\'
test\
Charles Beattie
fuente
14

Otro truco es usar chr (92) ya que se evalúa como "\".

Recientemente tuve que limpiar una cadena de barras invertidas y lo siguiente hizo el truco:

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

Me doy cuenta de que esto no se ocupa del "por qué", pero el hilo atrae a muchas personas que buscan una solución a un problema inmediato.

Geekworking
fuente
Pero, ¿qué pasa si la cadena original contiene barras invertidas?
Joseph Redfern
2
chr (92) es terriblemente oscuro, probablemente mejor usar "\\"(cadena no cruda con barra invertida)
clemep
9

Como \ "está permitido dentro de la cadena sin procesar. Entonces no se puede usar para identificar el final de la cadena literal.

¿Por qué no dejar de analizar el literal de cadena cuando te encuentras con el primer "?

Si ese fuera el caso, entonces \ "no estaría permitido dentro del literal de cadena. Pero lo es.

Brian R. Bondy
fuente
1
Exactamente. Los diseñadores de Python probablemente evaluaron la probabilidad de las dos alternativas: la secuencia de dos caracteres en \"cualquier lugar dentro de una cadena sin formato entre comillas dobles, O \ al final de la cadena sin formato entre comillas dobles. Las estadísticas de uso deben favorecer la secuencia de dos caracteres en cualquier lugar frente a la secuencia de un carácter al final.
placas
3

La razón por la cual la r'\'sintaxis es incorrecta es que, aunque la expresión de cadena es sin formato, las comillas utilizadas (simple o doble) siempre tienen que escapar, ya que de lo contrario marcarían el final de la cita. Entonces, si desea expresar una comilla simple dentro de una cadena entre comillas simples, no hay otra forma que usarla \'. Lo mismo se aplica para las comillas dobles.

Pero podrías usar:

'\\'
Gumbo
fuente
44
No responde 'por qué' :-)
cdleary
2

Otro usuario que desde entonces eliminó su respuesta (no estoy seguro de si desea que se les acredite) sugirió que los diseñadores del lenguaje Python podrían simplificar el diseño del analizador utilizando las mismas reglas de análisis y expandiendo los caracteres escapados a forma cruda como una ocurrencia tardía. (si el literal se marcó como crudo).

Pensé que era una idea interesante y la estoy incluyendo como wiki comunitaria para la posteridad.

cdleary
fuente
Pero podría permitirle evitar tener dos rutas de código de analizador de cadenas literales separadas.
cdleary
2

A pesar de su función, incluso una cadena sin procesar no puede terminar en una barra diagonal inversa simple, porque la barra diagonal inversa escapa al siguiente carácter de comillas: aún debe escapar del carácter de comillas circundante para incrustarlo en la cadena. Es decir, r "... \" no es un literal de cadena válido: una cadena sin procesar no puede terminar en un número impar de barras invertidas.
Si necesita terminar una cadena sin procesar con una barra invertida simple, puede usar dos y cortar la segunda.

pawandeep singh
fuente
1

Viniendo de C, es bastante claro para mí que un solo \ funciona como carácter de escape, lo que le permite poner caracteres especiales como líneas nuevas, pestañas y citas en cadenas.

De hecho, eso no permite \ como último personaje, ya que escapará al "y hará que el analizador se ahogue. Pero como se señaló anteriormente \ es legal.


fuente
1
Sí, el corazón del problema era que las cadenas sin formato tratan \ como un literal en lugar del comienzo de una secuencia de escape. Lo extraño es que todavía tiene propiedades de escape para citar, a pesar de ser tratado como un carácter literal.
cdleary
1

algunos consejos :

1) si necesita manipular la barra diagonal inversa para la ruta, el módulo estándar de Python os.path es su amigo. por ejemplo :

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

2) si desea construir cadenas con barra invertida PERO sin barra invertida al FINAL de su cadena, entonces la cadena sin procesar es su amigo (use el prefijo 'r' antes de su cadena literal). por ejemplo :

r'\one \two \three'

3) si necesita anteponer una cadena en una variable X con una barra diagonal inversa, puede hacer esto:

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) si necesita crear una cadena con una barra diagonal inversa al final, combine los consejos 2 y 3:

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

ahora lilypond_statement contiene "\DisplayLilyMusic \upper"

¡Viva Python! :)

n3on


fuente
1
Ninguno de estos responde a la pregunta de "por qué", pero no se deben usar los números 3 y 4. Cortar y agregar cadenas generalmente es una mala práctica, y debería preferir r '\ dummy' para el n. ° 3 (que funciona bien) y '' .join ([r '\ DisplayLilyMusic', r '\ upper']) al n. ° 4.
cdleary
1
La razón es que las cadenas son inmutables y cada corte / concatenación crea un nuevo objeto de cadena inmutable que generalmente se descarta. Es mejor acumularlos todos y unirlos en un solo paso con str.join (componentes)
cdleary
Oh, whoops, entendí mal lo que querías decir para el # 3 Creo que se prefiere una simple '\\' + X a crear una cadena solo para cortarla.
cdleary
Acaba de encontrar os.path.normpatheliminará la barra invertida tizón ... Entonces, ¿qué he de concat el nombre del archivo en el camino ...
Jing Él
0

Encontré este problema y encontré una solución parcial que es buena para algunos casos. A pesar de que Python no puede finalizar una cadena con una sola barra diagonal inversa, se puede serializar y guardar en un archivo de texto con una sola barra diagonal inversa al final. Por lo tanto, si lo que necesita es guardar un texto con una barra invertida en su computadora, es posible:

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)

Por cierto, no funciona con json si lo vuelcas usando la biblioteca json de python.

Finalmente, trabajo con Spyder, y noté que si abro la variable en el editor de texto de la araña haciendo doble clic en su nombre en el explorador de variables, se presenta con una barra invertida y se puede copiar al portapapeles de esa manera (no es muy útil para la mayoría de las necesidades, pero quizás para algunos ...).

Bossa Nova
fuente