Python複数行文字列の適切なインデント


456

関数内のPython複数行文字列の適切なインデントは何ですか?

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

または

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

または、他の何か?

最初の例では、文字列が関数の外にぶら下がっているのは奇妙に見えます。


4
docstringは特別に扱われます。最初の行のインデントは削除されます。他のすべての空白行ではない最小の共通インデントは、それらすべてから削除されます。それ以外では、Pythonの複数行の文字列リテラルは、空白に関しては残念ながら何を表示するかということです。文字列区切り文字の間のすべての文字は、Pythonが本能を読んでいるインデントを含め、文字列の一部になります。リテラルが始まる行のインデントから測定する必要があるように見えます。
Evgeni Sergeev

@EvgeniSergeev処理ツールがこのタスクを実行します(選択した処理ツールに大きく依存します)。method.__doc__他のどのstrリテラルよりもPython自体によって変更されることはありません。
CZ

回答:


453

おそらく、 """

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

改行とスペースは文字列自体に含まれているため、後処理する必要があります。それを望まず、大量のテキストがある場合は、テキストファイルに個別に保存することをお勧めします。テキストファイルがアプリケーションでうまく機能せず、後処理をしたくない場合は、おそらく

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

複数行の文字列を後処理して不要な部分をtextwrap削除する場合は、PEP 257で提示されているdocstringを後処理するためのモジュールまたは手法を検討する必要があります。

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)

10
これは、行の継続の「ぶら下げインデント」スタイルです。関数定義や長いifステートメントなどの目的でPEP8に規定されていますが、複数行の文字列については言及されていません。ぶら下げインデントは非常に嫌いで、プログラムの適切な構造がわかりにくくなっているため、個人的にこれはPEP8に従うことを拒否する1つの場所です(代わりに4スペースインデントを使用します)。
bobince

2
@buffer、公式チュートリアルの3.1.2(「隣り合った2つの文字列リテラルは自動的に連結されます...」)と言語リファレンス。
Mike Graham

5
自動文字列連結を使用した2番目のフォームには改行が含まれていませんこれは機能です。
Mike Graham

19
trim()PEP257に指定されている関数は次のように標準ライブラリに実装されていますinspect.cleandoc

2
ここでの「ぶら下げインデント」の拒否に関する+1から@bobinceへのコメント...特に、変数名をからstringtextまたは長さが異なるものに変更した場合、文字列のすべての単一行のインデントを更新する必要があります"""適切に一致させるための複数行の文字列。インデント戦略は、将来のリファクタリング/メンテナンスを複雑にするべきではなく、PEPが本当に失敗する場所の1つです
kevlarr

255

このtextwrap.dedent関数を使用すると、sourceの正しいインデントで開始し、使用する前にテキストからそれを取り除くことができます。

他の人が指摘しているように、これはリテラルに対する追加の関数呼び出しであることのトレードオフです。これらのリテラルをコードのどこに配置するかを決定するときは、これを考慮に入れてください。

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)

\ログメッセージリテラルの末尾は、改行がリテラルに含まれないようにするためです。このように、リテラルは空白行ではなく、次の完全な行で始まります。

からの戻り値textwrap.dedentは、すべての一般的な先行空白のインデントが文字列の各行で削除された入力文字列です。したがって、上記のlog_message値は次のようになります。

Prepare to frobnicate:
Here it comes...
    Any moment now.
And: Frobnicate!

2
これは合理的な解決策であり、知っておくと便利ですが、頻繁に呼び出される関数内でこのようなことを行うと、惨事になる可能性があります。
haridsv

@haridsvなぜそれが災害になるのでしょうか?
jtmoulia

10
@jtmoulia:textwrap.dedent()呼び出しの結果は入力引数と同じように定数値であるため、災害よりも適切な説明は「非効率的」です。
martineau 2012

2
@haridsvその災害/非効率の原因は、頻繁に呼び出される関数内の定数文字列を定義することです。呼び出しごとの定数定義を呼び出しごとのルックアップと交換できます。そうすれば、デント前処理は1回だけ実行されます。関連する質問は、stackoverflow.com / q / 15495376/611007のようになります。呼び出しごとに定数を定義しないようにするためのアイデアがリストされています。代替案はルックアップを必要とするようですが。それでも、それを保管するのに好ましい場所を見つけるためのさまざまな方法が試みられています。たとえばdef foo: return foo.x、次の行foo.x = textwrap.dedent("bar")です。
n611x007 2014

1
文字列が、デバッグモードでのみ有効になっているロギング用であり、それ以外の場合は未使用になる場合は、効率が悪いと思います。しかし、それではなぜとにかく複数行の文字列リテラルをログに記録するのでしょうか。したがって、これらの文字列を消費しているものはすべて遅くなるため、上記が非効率的である(つまり、プログラムがかなり遅くなる)現実の例を見つけるのは困難です。
Evgeni Sergeev

53

inspect.cleandocそのように使用してください:

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

相対的なインデントは期待どおりに維持されます。コメントあなたは、空行の前の使用を維持したい場合は、以下textwrap.dedent。ただし、最初の改行も保持されます。

注:構造を明確にするために、関連するコンテキストでコードの論理ブロックをインデントすることをお勧めします。たとえば、変数に属する複数行の文字列string


5
なぜこの答えが今まで存在しなかったのか、2008年のPython 2.6inspect.cleandoc以来ずっと存在していたので混乱しています。特にぶら下げインデントスタイルを使用しないため、絶対に最もクリーンな答えです。これは、不必要なスペースを浪費するだけです
kevlarr

1
このソリューションは、空白のテキストの最初の数行を削除します(ある場合)。その動作を望まない場合は、textwrap.dedent docs.python.org/2/library/textwrap.html#textwrap.dedent
joshuakcockrell

1
これは完璧です!
zzzz zzzz

23

他の回答から欠落しているように見える1つのオプション(naxaによるコメントでのみ詳細に言及)は次のとおりです。

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

これにより、適切な配置が可能になり、行を暗黙的に結合し、行シフトを維持します。これは、私にとって、とにかく複数行の文字列を使用したい理由の1つです。

後処理は必要ありませんが\n、行を終了する任意の場所に手動でを追加する必要があります。インラインまたは後の別の文字列として。後者はコピーして貼り付ける方が簡単です。


これは暗黙的に結合された文字列の例であり、複数行の文字列ではないことに注意してください。
trk

@trk、それは文字列に改行(別名複数行)が含まれているという意味では複数行ですが、はい、結合を使用してOPで発生したフォーマットの問題を回避します。
holroy

17

さらにいくつかのオプション。pylabを有効にしたIpythonでは、dedentはすでに名前空間にあります。私がチェックしたところ、それはmatplotlibからのものです。または、次のコマンドでインポートできます。

from matplotlib.cbook import dedent

ドキュメンテーションでは、同等のtextwrapよりも高速であると述べており、ipythonでの私のテストでは、私のクイックテストで実際に平均で3倍高速です。また、先頭の空白行を破棄するという利点もあります。これにより、文字列の作成方法に柔軟性を持たせることができます。

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

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

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

これら3つの例でmatplotlib dedentを使用すると、同じ実用的な結果が得られます。textwrap dedent関数には、最初の例のように先行する空白行があります。

明らかな欠点は、matwrapplibが外部モジュールであるのに対して、textwrapが標準ライブラリにあることです。

ここにいくつかのトレードオフがあります... dedent関数は、文字列が定義される場所でコードを読みやすくしますが、使用可能な形式で文字列を取得するには後で処理する必要があります。docstringでは、ほとんどの使用で必要な処理が行われるため、正しいインデントを使用する必要があることは明らかです。

コードに長い文字列が必要な場合、次の明らかに醜いコードを見つけます。長い文字列を囲んでいるインデントから削除します。「Beautifulは醜いよりも優れている」という点で間違いなく失敗しますが、それは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()

6

すばやく簡単な解決策が必要で、改行を入力する手間を省く場合は、代わりにリストを選択できます。例:

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

これは最善の方法ではありませんが、私は時々それを使用しました。あなたがいる場合行うそれを使用することが接合される前に変更するつもりはないので、あなたは、リストの代わりにタプルを使用する必要があります。
Lyndsy Simon 2018

4

私は好む

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

または

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

1
(関数内の)インデントが重要であることを質問が明示的に述べているため、これは質問に答えません。
bignose 2017

@bignoseこの質問は、「変に見える」との使用を禁止されていません。
lk_vc 2017

醜いインデントなしでこれをどのように達成できますか?
lfender6445 2017年

@ lfender6445よく、おそらくこれらすべての文字列を他のコードとは別のファイルに配置できます...
lk_vc

3

私の2セントは、インデントを取得するために行末をエスケープします。

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

1

たとえば、スクリプト内で「関数の外側にぶら下がる」など、乱雑に見せることなく、印刷用のdocstringの識別レベル削除/修正するための簡単な1行を探してここに来ました。

これが私がやったことです:

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

明らかに、タブキーではなくスペース(たとえば4)でインデントしている場合は、代わりに次のようなものを使用します。

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

また、docstringを次のようにしたい場合は、最初の文字を削除する必要はありません。

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

print string.replace(myfunction.__doc__,'\n\t','\n') 

これは、クラスメソッドとネストされたクラスでは失敗します。
tacaswell 14

1

最初のオプションは良いものです-インデントが含まれています。Pythonスタイルです-コードを読みやすくします。

正しく表示するには:

print string.lstrip()

あなたが原因インデントに余分なスペースがありませんので、これはトリプル引用文字列をフォーマットする最も簡単かつクリーンな方法のように思える
テイラーリスを

4
これにより、複数行文字列の最初の行の先頭のスペースのみが削除されます。次の行のフォーマットには役立ちません。
M.シュレンカー

0

テキストの表示方法によって異なります。すべてを左揃えにする場合は、最初のスニペットのようにフォーマットするか、すべてのスペースを左トリミングする行を繰り返します。


5
道のdocstring加工ツールの仕事はありません削除することですすべての左側のスペースを、しかし同じくらい最初のインデント行として。この戦略はもう少し洗練されており、後処理された文字列でインデントしてそれを尊重することができます。
マイクグラハム、

0

文字列の場合、文字列を処理した直後にできます。docstringの場合は、代わりに関数を後処理する必要があります。以下は、両方とも解決できる、両方の解決策です。

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__

1
docstringの処理は、PEP 257で説明されているように、一貫したインデントをすでに処理している必要があります。これをinspect.cleandoc正しく行うツールなどがすでにあります。
bignose 2017

0

私は同様の問題を抱えています、コードは複数行を使用して本当に読みにくくなりました、私は何かのように出てきました

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

はい、最初はひどく見えるかもしれませんが、埋め込まれた構文は非常に複雑で、最後に何かを追加すること( '\ n "'など)は解決策ではありませんでした


0

この関数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


"""))

結果:

"""
line one
    line two
        line three
    line two
line one
"""
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.