Recuo adequado para cadeias de caracteres multilinha do Python

456

Qual é o recuo adequado para seqüências multilinhas Python dentro de uma função?

    def method():
        string = """line one
line two
line three"""

ou

    def method():
        string = """line one
        line two
        line three"""

ou alguma outra coisa?

Parece meio estranho ter a string pendurada fora da função no primeiro exemplo.

prender
fonte
4
As doutrinas são tratadas especialmente : qualquer travessão da primeira linha é removido; o menor recuo comum assumido sobre todas as outras linhas não em branco é removido de todas. Além disso, os literais de cadeia de caracteres de várias linhas no Python são infelizmente o que você vê em termos de espaço em branco: todos os caracteres entre os delimitadores de cadeia de caracteres se tornam parte da cadeia de caracteres, incluindo indentação que, com instintos de leitura do Python, parece que deve ser medido a partir do recuo da linha onde o literal começa.
Evgeni Sergeev 02/09
@EvgeniSergeev A ferramenta de processamento executa esta tarefa (e isso depende muito da sua escolha da ferramenta de processamento). method.__doc__não é modificado pelo próprio Python mais do que qualquer outro strliteral.
CZ

Respostas:

453

Você provavelmente deseja alinhar com o """

def foo():
    string = """line one
             line two
             line three"""

Como as novas linhas e os espaços estão incluídos na própria string, você deverá processá-la posteriormente. Se você não quiser fazer isso e tiver muito texto, pode armazená-lo separadamente em um arquivo de texto. Se um arquivo de texto não funcionar bem para o seu aplicativo e você não quiser pós-processamento, eu provavelmente usaria

def foo():
    string = ("this is an "
              "implicitly joined "
              "string")

Se você deseja pós-processar uma sequência de múltiplas linhas para aparar as partes desnecessárias, considere o textwrapmódulo ou a técnica para pós-processamento de documentos apresentados no PEP 257 :

def trim(docstring):
    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)
Mike Graham
fonte
10
Este é o estilo 'indentação suspensa' da continuação da linha. É prescrito no PEP8 para fins como definições de função e instruções long if, embora não sejam mencionadas para cadeias de linhas múltiplas. Pessoalmente, este é um lugar em que me recuso a seguir o PEP8 (e, em vez disso, uso o recuo de 4 espaços), pois não gosto de recuos pendurados, o que, para mim, obscurece a estrutura apropriada do programa.
bobince
2
@buffer, no 3.1.2 do tutorial oficial ("Duas literais de string ao lado da outra são automaticamente concatenadas ...") e na referência de idioma.
Mike Graham
5
O segundo formulário com concatenação automática de strings não inclui nova linha. É um recurso.
Mike Graham
19
A trim()função especificada no PEP257 é implementada na biblioteca padrão como inspect.cleandoc.
2
Marque +1 no comentário de @bobince sobre a rejeição de "recuos pendurados" aqui ... Especialmente porque se você alterar o nome da variável de stringpara textou algo de tamanho diferente, será necessário atualizar o recuo de literalmente todas as linhas do cadeia de múltiplas linhas apenas para que ela corresponda """corretamente. Estratégia de recuo não deve complicar refatora futuros / manutenção, e é um dos lugares que PEP realmente falhar
kevlarr
255

A textwrap.dedentfunção permite que você comece com o recuo correto na fonte e, em seguida, retire-o do texto antes de usá-lo.

A desvantagem, como observado por alguns outros, é que essa é uma chamada de função extra no literal; leve isso em consideração ao decidir onde colocar esses literais no seu 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)

O final \do literal da mensagem de log é garantir que a quebra de linha não esteja no literal; dessa forma, o literal não começa com uma linha em branco e, em vez disso, começa com a próxima linha completa.

O valor de retorno de textwrap.dedenté a sequência de entrada com todo o recuo de espaço em branco inicial comum removido em cada linha da sequência. Portanto, o log_messagevalor acima será:

Prepare to frobnicate:
Here it comes...
    Any moment now.
And: Frobnicate!
nariz grande
fonte
2
Embora seja uma solução razoável e agradável de se saber, fazer algo assim dentro de uma função freqüentemente chamada pode ser um desastre.
haridsv
@haridsv Por que isso seria um desastre?
Jtmoulia
10
@ jtmoulia: Uma descrição melhor do que um desastre seria "ineficiente" porque o resultado da textwrap.dedent()chamada é um valor constante, assim como seu argumento de entrada.
martineau
2
@haridsv a origem desse desastre / ineficiência está definindo uma cadeia constante dentro de uma função frequentemente chamada. É possível negociar a definição constante por chamada para uma consulta por chamada. Dessa forma, o pré-processamento dedent seria executado apenas uma vez . Uma pergunta relevante pode ser stackoverflow.com/q/15495376/611007 Ela lista idéias para evitar a definição da constante para cada chamada. Embora as alternativas pareçam exigir uma pesquisa. Ainda assim, são tentadas várias maneiras de encontrar o local favorável para armazená-lo. Por exemplo: def foo: return foo.xdepois na próxima linha foo.x = textwrap.dedent("bar").
N611x007
1
Eu acho que seria ineficiente se a string for destinada ao log que é habilitado apenas no modo de depuração e não for utilizada de outra forma. Mas então por que registrar uma string multilinha literal de qualquer maneira? Portanto, é difícil encontrar um exemplo da vida real em que o acima exposto seria ineficiente (ou seja, onde diminui consideravelmente o programa), porque o que estiver consumindo essas seqüências será mais lento.
Evgeni Sergeev
53

Use inspect.cleandocassim:

def method():
    string = inspect.cleandoc("""
        line one
        line two
        line three""")

O recuo relativo será mantido conforme o esperado. Conforme comentado abaixo, se você deseja manter as linhas vazias anteriores, use textwrap.dedent. No entanto, isso também mantém a primeira quebra de linha.

Nota: É uma boa prática recuar blocos lógicos de código em seu contexto relacionado para esclarecer a estrutura. Por exemplo, a sequência de linhas múltiplas pertencente à variável string.

wihlke
fonte
5
Tão confuso por que essa resposta não existia até agora, inspect.cleandocexiste desde o Python 2.6 , que foi em 2008 ..? Absolutamente a resposta mais limpo, especialmente porque ele não usa o estilo recuo deslocado, o que só desperdiça uma quantidade desnecessária de espaço
kevlarr
1
Esta solução remove as primeiras linhas do texto em branco (se houver). Se você não quer que o comportamento, o uso textwrap.dedent docs.python.org/2/library/textwrap.html#textwrap.dedent
joshuakcockrell
1
Isto é perfeito!
zzzz zzzz 21/02
23

Uma opção que parece faltar nas outras respostas (mencionada apenas no fundo por um comentário de naxa) é a seguinte:

def foo():
    string = ("line one\n"          # Add \n in the string
              "line two"  "\n"      # Add "\n" after the string
              "line three\n")

Isso permitirá o alinhamento adequado, unirá as linhas implicitamente e ainda manterá a mudança de linha, o que, para mim, é uma das razões pelas quais eu gostaria de usar seqüências de múltiplas linhas de qualquer maneira.

Não requer pós-processamento, mas você precisa adicionar manualmente o \nlocal em que deseja que a linha termine. Inline ou como uma sequência separada depois. O último é mais fácil de copiar e colar.

Holroy
fonte
Observe que este é um exemplo de uma sequência associada implicitamente, não de uma sequência multilinha.
trk
@trk, é multilinha no sentido de que a string contém novas linhas (também conhecidas como várias linhas), mas sim, ela usa a junção para contornar os problemas de formatação que o OP tinha.
holroy
17

Mais algumas opções. No Ipython com o pylab ativado, dedent já está no espaço para nome. Eu verifiquei e é do matplotlib. Ou pode ser importado com:

from matplotlib.cbook import dedent

Na documentação, afirma que é mais rápido que o equivalente a textwrap e, nos meus testes no ipython, é de fato três vezes mais rápido, em média, nos meus testes rápidos. Ele também tem o benefício de descartar qualquer linha em branco inicial, permitindo que você seja flexível na maneira como constrói a string:

"""
line 1 of string
line 2 of string
"""

"""\
line 1 of string
line 2 of string
"""

"""line 1 of string
line 2 of string
"""

O uso da dedução do matplotlib nesses três exemplos fornecerá o mesmo resultado sensato. A função dedent de quebra de texto terá uma linha em branco à esquerda com o 1º exemplo.

A desvantagem óbvia é que o textwrap está na biblioteca padrão enquanto o matplotlib é um módulo externo.

Algumas desvantagens aqui ... as funções dedent tornam seu código mais legível onde as strings são definidas, mas requerem processamento posterior para obter a string no formato utilizável. Nas docstrings, é óbvio que você deve usar o recuo correto, pois a maioria dos usos da docstring fará o processamento necessário.

Quando eu preciso de uma sequência não longa no meu código, encontro o seguinte código confessamente feio, em que deixo a sequência longa sair do recuo anexo. Definitivamente falha em "Bonito é melhor que feio.", Mas alguém poderia argumentar que é mais simples e mais explícito do que a alternativa dedente.

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()
Joop
fonte
6

Se você deseja uma solução rápida e fácil e evita digitar novas linhas, pode optar por uma lista, por exemplo:

def func(*args, **kwargs):
    string = '\n'.join([
        'first line of very long string and',
        'second line of the same long thing and',
        'third line of ...',
        'and so on...',
        ])
    print(string)
    return
Steabert
fonte
Embora essa não seja a melhor abordagem, eu a uso periodicamente. Se você fazer usá-lo, você deve usar uma tupla em vez de uma lista, uma vez que não vai ser modificado antes de serem unidas.
Lyndsy Simon
4

eu prefiro

    def method():
        string = \
"""\
line one
line two
line three\
"""

ou

    def method():
        string = """\
line one
line two
line three\
"""
lk_vc
fonte
1
Isso não responde à pergunta, porque a pergunta afirma explicitamente que o recuo (dentro da função) é importante.
precisa saber é
@bignose A pergunta dizia: "Parece meio estranho" não é permitido usar.
Lk_vc 15/08/19
como eu conseguiria isso sem a indentação feia?
Lfender6445 7/07
@ lfender6445 bem, talvez você pode colocar todas essas cordas para um arquivo separado de outros códigos ...
lk_vc
3

Meus dois centavos, escapam do final da linha para obter os recuos:

def foo():
    return "{}\n"\
           "freq: {}\n"\
           "temp: {}\n".format( time, freq, temp )
Simon
fonte
1

Eu vim aqui procurando um liner simples para remover / corrigir o nível de identificação do docstring para impressão, sem torná-lo desarrumado , por exemplo, deixando-o "fora da função" dentro do script.

Aqui está o que eu acabei fazendo:

import string
def myfunction():

    """
    line 1 of docstring
    line 2 of docstring
    line 3 of docstring"""

print str(string.replace(myfunction.__doc__,'\n\t','\n'))[1:] 

Obviamente, se você estiver recuando com espaços (por exemplo, 4) em vez da tecla tab, use algo como isto:

print str(string.replace(myfunction.__doc__,'\n    ','\n'))[1:]

E você não precisa remover o primeiro caractere se quiser que seus documentos sejam assim:

    """line 1 of docstring
    line 2 of docstring
    line 3 of docstring"""

print string.replace(myfunction.__doc__,'\n\t','\n') 
James Gowdy
fonte
Isso falha nos métodos de classe e nas classes aninhadas.
precisa saber é o seguinte
1

A primeira opção é a boa - com recuo incluído. É no estilo python - fornece legibilidade para o código.

Para exibi-lo corretamente:

print string.lstrip()
Bog Dia
fonte
Esta parece ser a maneira mais simples e mais limpa para formatar cordas cotação triplos para que você não tem os espaços extras devido ao recuo
Taylor Liss
4
Isso excluirá apenas espaços à esquerda na primeira linha de uma seqüência de linhas múltiplas. Não ajuda na formatação das seguintes linhas.
Schlenker
0

Depende de como você deseja que o texto seja exibido. Se você quiser que tudo fique alinhado à esquerda, formate-o como no primeiro trecho ou faça uma iteração pelas linhas, aparando à esquerda todo o espaço.

Ignacio Vazquez-Abrams
fonte
5
A forma como as ferramentas de processamento de DocString trabalho é remover não todo o espaço à esquerda, mas tanto como a primeira linha recuada. Essa estratégia é um pouco mais sofisticada e permite recuar e respeitá-la na string pós-processada.
Mike Graham
0

Para seqüências de caracteres, você pode processar a sequência logo após. Para documentos, você precisa depois processar a função. Aqui está uma solução para ambos que ainda é legível.

class Lstrip(object):
    def __rsub__(self, other):
        import re
        return re.sub('^\n', '', re.sub('\n$', '', re.sub('\n\s+', '\n', other)))

msg = '''
      Lorem ipsum dolor sit amet, consectetur adipiscing 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 ex ea
      commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
      velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
      cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
      est laborum.
      ''' - Lstrip()

print msg

def lstrip_docstring(func):
    func.__doc__ = func.__doc__ - Lstrip()
    return func

@lstrip_docstring
def foo():
    '''
    Lorem ipsum dolor sit amet, consectetur adipiscing 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 ex ea
    commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
    velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
    cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
    est laborum.
    '''
    pass


print foo.__doc__
lagartixas
fonte
1
As docstrings de processamento já devem processar recuo consistente, conforme descrito no PEP 257 . Já existem ferramentas - por exemplo inspect.cleandoc- que fazem isso da maneira certa.
precisa saber é
0

Estou tendo um problema semelhante, o código ficou realmente ilegível usando multilinhas, eu criei algo como

print("""aaaa
"""   """bbb
""")

sim, no começo poderia parecer terrível, mas a sintaxe incorporada era bastante complexa e adicionar algo no final (como '\ n "') não era uma solução

Frediano Ziglio
fonte
0

Você pode usar esta função trim_indent .

import re


def trim_indent(s: str):
    s = re.sub(r'^\n+', '', s)
    s = re.sub(r'\n+$', '', s)
    spaces = re.findall(r'^ +', s, flags=re.MULTILINE)
    if len(spaces) > 0 and len(re.findall(r'^[^\s]', s, flags=re.MULTILINE)) == 0:
        s = re.sub(r'^%s' % (min(spaces)), '', s, flags=re.MULTILINE)
    return s


print(trim_indent("""


        line one
            line two
                line three
            line two
        line one


"""))

Resultado:

"""
line one
    line two
        line three
    line two
line one
"""
Luan Silveira
fonte