¿Sangría adecuada para cadenas de varias líneas?
¿Cuál es la sangría adecuada para cadenas multilínea de Python dentro de una función?
def method():
string = """line one
line two
line three"""
o
def method():
string = """line one
line two
line three"""
¿o algo mas?
Parece un poco extraño tener la cuerda colgando fuera de la función en el primer ejemplo.
Probablemente quieras alinearte con el"""
def foo():
string = """line one
line two
line three"""
Dado que las nuevas líneas y los espacios están incluidos en la propia cadena, tendrás que posprocesarla. Si no desea hacer eso y tiene una gran cantidad de texto, es posible que desee almacenarlo por separado en un archivo de texto. Si un archivo de texto no funciona bien para su aplicación y no desea posprocesarlo, probablemente optaría por
def foo():
string = ("this is an "
"implicitly joined "
"string")
Si desea posprocesar una cadena multilínea para recortar las partes que no necesita, debe considerar el textwrap
módulo o la técnica para posprocesar cadenas de documentos presentada en PEP 257 :
def trim(docstring):
import sys
if not docstring:
return ''
# Convert tabs to spaces (following the normal Python rules)
# and split into a list of lines:
lines = docstring.expandtabs().splitlines()
# Determine minimum indentation (first line doesn't count):
indent = sys.maxint
for line in lines[1:]:
stripped = line.lstrip()
if stripped:
indent = min(indent, len(line) - len(stripped))
# Remove indentation (first line is special):
trimmed = [lines[0].strip()]
if indent < sys.maxint:
for line in lines[1:]:
trimmed.append(line[indent:].rstrip())
# Strip off trailing and leading blank lines:
while trimmed and not trimmed[-1]:
trimmed.pop()
while trimmed and not trimmed[0]:
trimmed.pop(0)
# Return a single string:
return '\n'.join(trimmed)
La textwrap.dedent
función permite comenzar con la sangría correcta en la fuente y luego eliminarla del texto antes de usarla.
La desventaja, como han señalado algunos otros, es que se trata de una llamada de función adicional en el literal; tenga esto en cuenta al decidir dónde colocar estos literales en su código.
import textwrap
def frobnicate(param):
""" Frobnicate the scrognate param.
The Weebly-Ruckford algorithm is employed to frobnicate
the scrognate to within an inch of its life.
"""
prepare_the_comfy_chair(param)
log_message = textwrap.dedent("""\
Prepare to frobnicate:
Here it comes...
Any moment now.
And: Frobnicate!""")
weebly(param, log_message)
ruckford(param)
El final \
del mensaje de registro literal es para garantizar que el salto de línea no esté en el literal; de esa manera, el literal no comienza con una línea en blanco, sino que comienza con la siguiente línea completa.
El valor de retorno de textwrap.dedent
es la cadena de entrada con todos los espacios en blanco iniciales comunes eliminados en cada línea de la cadena. Entonces el log_message
valor anterior será:
Prepare to frobnicate:
Here it comes...
Any moment now.
And: Frobnicate!
Usar inspect.cleandoc()
:
import inspect
def method():
string = inspect.cleandoc("""
line one
line two
line three""")
Qué inspect.cleandoc()
y textwrap.dedent()
ambos hacen:
- contar los espacios iniciales de la línea no vacía con menor sangría
- eliminar ese recuento de espacios de cada línea que no esté vacía
¿ Qué inspect.cleandoc()
hace además?
- eliminar el inicio y el final
\n
del resultado
Nota: Es una buena práctica sangrar los bloques lógicos de código bajo su contexto relacionado para aclarar la estructura. Por ejemplo, la cadena de varias líneas que pertenece a la variable
string
.
Una opción que parece faltar en las otras respuestas (solo mencionada en un comentario de naxa) es la siguiente:
def foo():
string = ("line one\n" # Add \n in the string
"line two" "\n" # Add "\n" after the string
"line three\n")
Esto permitirá una alineación adecuada, unirá las líneas implícitamente y aún mantendrá el desplazamiento de línea que, para mí, es una de las razones por las que me gustaría usar cadenas multilínea de todos modos.
No requiere ningún posprocesamiento, pero debe agregar manualmente \n
en cualquier lugar determinado donde desee que termine la línea. Ya sea en línea o como una cadena separada después. Este último es más fácil de copiar y pegar.
Algunas opciones más. En Ipython con pylab habilitado, dedent ya está en el espacio de nombres. Lo verifiqué y es de matplotlib. O se puede importar con:
from matplotlib.cbook import dedent
En la documentación se indica que es más rápido que el equivalente de ajuste de texto y en mis pruebas en ipython es de hecho 3 veces más rápido en promedio con mis pruebas rápidas. También tiene la ventaja de que descarta cualquier línea en blanco inicial, lo que le permite ser flexible en la forma de construir la cadena:
"""
line 1 of string
line 2 of string
"""
"""\
line 1 of string
line 2 of string
"""
"""line 1 of string
line 2 of string
"""
El uso de matplotlib dedent en estos tres ejemplos dará el mismo resultado sensato. La función de ajuste de texto tendrá una línea en blanco inicial con el primer ejemplo.
La desventaja obvia es que textwrap está en la biblioteca estándar, mientras que matplotlib es un módulo externo.
Algunas compensaciones aquí... las funciones dedent hacen que su código sea más legible donde se definen las cadenas, pero requieren procesamiento posterior para obtener la cadena en un formato utilizable. En las cadenas de documentación, es obvio que debe utilizar la sangría correcta, ya que la mayoría de los usos de la cadena de documentación realizarán el procesamiento requerido.
Cuando necesito una cadena no larga en mi código, encuentro el siguiente código ciertamente feo en el que dejo que la cadena larga salga de la sangría adjunta. Definitivamente falla en "Lo hermoso es mejor que lo feo", pero se podría argumentar que es más simple y explícito que la alternativa dedent.
def example():
long_string = '''\
Lorem ipsum dolor sit amet, consectetur adipisicing
elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua. Ut enim ad minim veniam, quis
nostrud exercitation ullamco laboris nisi ut aliquip.\
'''
return long_string
print example()