Indentasi yang tepat untuk string multiline Python


456

Apa lekukan yang tepat untuk string multiline Python dalam suatu fungsi?

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

atau

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

atau sesuatu yang lain?

Tampaknya agak aneh memiliki string yang tergantung di luar fungsi pada contoh pertama.


4
Docstrings diperlakukan secara khusus : indentasi dari baris pertama dihapus; indentasi umum terkecil yang diambil alih semua saluran non-kosong dihapus dari semuanya. Selain itu, literal string multiline dalam Python sayangnya apa-yang-Anda-lihat-apa-yang-Anda-dapatkan dalam hal spasi putih: semua karakter antara pembatas string menjadi bagian dari string, termasuk lekukan itu, dengan naluri membaca Python, Sepertinya itu harus diukur dari indentasi garis di mana literal dimulai.
Evgeni Sergeev

@EvgeniSergeev Alat pengolah melakukan tugas ini (dan itu sangat tergantung pada pilihan alat pengolah Anda). method.__doc__tidak dimodifikasi oleh Python sendiri lebih dari strliteral lainnya .
cz

Jawaban:


453

Anda mungkin ingin berbaris dengan """

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

Karena baris baru dan spasi termasuk dalam string itu sendiri, Anda harus mempostingnya. Jika Anda tidak ingin melakukan itu dan Anda memiliki banyak teks, Anda mungkin ingin menyimpannya secara terpisah dalam file teks. Jika file teks tidak berfungsi dengan baik untuk aplikasi Anda dan Anda tidak ingin memposting, saya mungkin akan menggunakannya

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

Jika Anda ingin memposting proses multi-string untuk memangkas bagian-bagian yang tidak Anda butuhkan, Anda harus mempertimbangkan textwrapmodul atau teknik untuk pemrosesan pascaproses yang disajikan dalam 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)

10
Ini adalah gaya 'menggantung indent' dari kelanjutan garis. Ini ditentukan dalam PEP8 untuk tujuan seperti definisi fungsi dan pernyataan panjang jika, meskipun tidak disebutkan untuk string multiline. Secara pribadi ini adalah satu tempat saya menolak untuk mengikuti PEP8 (dan menggunakan indentasi 4-ruang sebagai gantinya), karena saya sangat tidak menyukai indentasi gantung, yang bagi saya mengaburkan struktur program yang tepat.
bobince

2
@buffer, dalam 3.1.2 dari tutorial resmi ("Dua string literal yang bersebelahan secara otomatis digabungkan ...") dan dalam referensi bahasa.
Mike Graham

5
Bentuk kedua dengan penggabungan string otomatis tidak termasuk baris baru. Ini fitur.
Mike Graham

19
The trim()berfungsi sebagai ditentukan dalam PEP257 diimplementasikan di perpustakaan standar sebagai inspect.cleandoc.

2
Beri +1 ke komentar @bobince tentang menolak "indentasi gantung" di sini ... Terutama karena jika Anda mengubah nama variabel dari stringmenjadi textatau apa pun dengan panjang yang berbeda, maka Anda sekarang perlu memperbarui lekukan dari setiap baris dari string multiline hanya untuk membuatnya cocok dengan yang """benar. Strategi lekukan tidak boleh menyulitkan refaktor / pemeliharaan di masa depan, dan itu adalah salah satu tempat yang PEP benar-benar gagal
kevlarr

255

The textwrap.dedentFungsi memungkinkan seseorang untuk memulai dengan lekukan yang benar dalam sumber , dan kemudian strip itu dari teks sebelum digunakan.

Pertukarannya, sebagaimana dicatat oleh beberapa orang lain, adalah bahwa ini adalah panggilan fungsi tambahan pada literal; memperhitungkan ini ketika memutuskan di mana menempatkan literal ini dalam kode Anda.

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)

Trailing \dalam literal pesan log adalah untuk memastikan bahwa baris tidak ada dalam literal; dengan cara itu, literal tidak dimulai dengan baris kosong, dan sebaliknya mulai dengan baris penuh berikutnya.

Nilai kembali dari textwrap.dedentadalah string input dengan semua indentasi spasi putih terkemuka yang umum dihapus pada setiap baris string. Jadi nilai di atas log_messageadalah:

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

2
Meskipun ini adalah solusi yang masuk akal dan menyenangkan untuk diketahui, melakukan sesuatu seperti ini di dalam fungsi yang sering disebut dapat terbukti menjadi bencana.
haridsv

@haridsv Mengapa itu menjadi bencana?
jtmoulia

10
@ jtmoulia: Deskripsi yang lebih baik daripada bencana akan "tidak efisien" karena hasil textwrap.dedent()panggilan adalah nilai konstan, sama seperti argumen inputnya.
martineau

2
@haridsv asal dari bencana / ketidakefisienan tersebut adalah mendefinisikan string konstan di dalam fungsi yang sering disebut. Memungkinkan untuk memperdagangkan definisi konstan per panggilan untuk pencarian per panggilan. Dengan begitu preprocessing dedent akan berjalan hanya sekali . Pertanyaan yang relevan mungkin adalah stackoverflow.com/q/15495376/611007. Ia mencantumkan ide untuk menghindari penetapan konstanta per setiap panggilan. Meskipun alternatif tampaknya membutuhkan pencarian. Meski demikian, berbagai cara untuk menemukan tempat yang menguntungkan untuk menyimpannya masih dicoba. Misalnya: def foo: return foo.xkemudian baris berikutnya foo.x = textwrap.dedent("bar").
n611x007

1
Saya kira itu akan menjadi tidak efisien jika string dimaksudkan untuk logging yang hanya diaktifkan dalam mode debug, dan tidak digunakan sebaliknya. Tapi mengapa tetap membuat string multiline literal? Jadi sulit untuk menemukan contoh kehidupan nyata di mana hal di atas tidak efisien (yaitu di mana ia sangat memperlambat program), karena apa pun yang memakan string ini akan menjadi lebih lambat.
Evgeni Sergeev

53

Gunakan inspect.cleandocseperti ini:

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

Lekukan relatif akan dipertahankan seperti yang diharapkan. Seperti yang dikomentari di bawah ini, jika Anda ingin tetap menggunakan baris sebelumnya, gunakan textwrap.dedent. Namun itu juga menjaga jeda baris pertama.

Catatan: Ini praktik yang baik untuk membuat blok kode logis di bawah konteks terkait untuk memperjelas struktur. Misalnya string multi-line milik variabel string.


5
Jadi bingung mengapa jawaban ini tidak ada sampai sekarang, inspect.cleandocsudah ada sejak Python 2.6 , yaitu 2008 ..? Benar-benar jawaban terbersih, terutama karena itu tidak menggunakan gaya indentasi gantung, yang hanya membuang ruang yang tidak perlu
kevlarr

1
Solusi ini menghapus beberapa baris pertama teks kosong (jika ada). Jika Anda tidak menginginkan perilaku itu, gunakan textwrap.dedent docs.python.org/2/library/textwrap.html#textwrap.dedent
joshuakcockrell

1
Ini sempurna!
zzzz zzzz

23

Satu opsi yang tampaknya hilang dari jawaban lain (hanya disebutkan jauh di dalam komentar oleh naxa) adalah sebagai berikut:

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

Ini akan memungkinkan penyelarasan yang tepat, bergabung dengan garis secara implisit, dan masih menjaga pergeseran garis yang, bagi saya, adalah salah satu alasan mengapa saya ingin tetap menggunakan string multiline.

Itu tidak memerlukan postprocessing apa pun, tetapi Anda perlu menambahkan secara manual \ndi tempat tertentu yang Anda inginkan untuk mengakhiri garis. Baik sebaris atau sebagai string terpisah setelah. Yang terakhir lebih mudah untuk di-copy-paste.


Perhatikan bahwa ini adalah contoh dari string yang bergabung secara implisit, bukan string multiline.
trk

@ trk, itu multiline dalam arti bahwa string berisi baris baru (alias beberapa baris), tapi ya itu menggunakan bergabung untuk menghindari masalah format yang dimiliki OP.
holroy

17

Beberapa opsi lagi. Di Ipython dengan pylab diaktifkan, dedent sudah ada di namespace. Saya memeriksa dan itu dari matplotlib. Atau dapat diimpor dengan:

from matplotlib.cbook import dedent

Dalam dokumentasi itu menyatakan bahwa itu lebih cepat daripada yang setara dengan textwrap dan dalam tes saya di ipython memang 3 kali lebih cepat rata-rata dengan tes cepat saya. Ini juga memiliki keuntungan bahwa ia membuang setiap baris kosong terkemuka yang memungkinkan Anda untuk fleksibel dalam cara Anda membangun 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
"""

Menggunakan dedikasi matplotlib pada ketiga contoh ini akan memberikan hasil yang masuk akal yang sama. Fungsi deduksi tulisan teks akan memiliki baris kosong terkemuka dengan contoh 1.

Kerugian yang jelas adalah bahwa textwrap di perpustakaan standar sementara matplotlib adalah modul eksternal.

Beberapa tradeoffs di sini ... fungsi dedent membuat kode Anda lebih mudah dibaca di mana string didefinisikan, tetapi membutuhkan pemrosesan nanti untuk mendapatkan string dalam format yang dapat digunakan. Dalam dokumentasi jelas bahwa Anda harus menggunakan indentasi yang benar karena sebagian besar penggunaan docstring akan melakukan pemrosesan yang diperlukan.

Ketika saya membutuhkan string yang tidak panjang dalam kode saya, saya menemukan kode jelek berikut ini di mana saya membiarkan string panjang keluar dari lekukan terlampir. Jelas gagal pada "Cantik lebih baik daripada jelek.", Tetapi orang bisa berpendapat bahwa itu lebih sederhana dan lebih eksplisit daripada alternatif yang ditentukan.

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

Jika Anda menginginkan solusi cepat & mudah dan menyelamatkan diri dari mengetik baris baru, Anda bisa memilih daftar, misalnya:

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

Meskipun ini bukan pendekatan terbaik, saya telah menggunakannya dari waktu ke waktu. Jika Anda melakukannya menggunakannya, Anda harus menggunakan tuple bukannya daftar, karena itu tidak akan diubah sebelum bergabung.
Lyndsy Simon

4

saya lebih memilih

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

atau

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

1
Ini tidak menjawab pertanyaan, karena pertanyaan secara eksplisit menyatakan bahwa lekukan (dalam fungsi) penting.
bignose

@ Bignose Pertanyaannya mengatakan "Sepertinya aneh" tidak diijinkan untuk digunakan.
lk_vc

bagaimana saya bisa melakukan ini tanpa lekukan jelek?
lfender6445

@ lfender6445 well, mungkin Anda dapat menempatkan semua string ini ke file terpisah dari kode lain ...
lk_vc

3

Dua sen saya, lepas dari garis akhir untuk mendapatkan indentasi:

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

1

Saya datang ke sini mencari 1-liner sederhana untuk menghapus / memperbaiki tingkat identifikasi dokumen untuk dicetak, tanpa membuatnya terlihat berantakan , misalnya dengan membuatnya "menggantung fungsi" di dalam skrip.

Inilah yang akhirnya saya lakukan:

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

Jelas, jika Anda membuat indentasi dengan spasi (mis. 4) alih-alih tombol tab gunakan sesuatu seperti ini sebagai gantinya:

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

Dan Anda tidak perlu menghapus karakter pertama jika Anda suka dokumen Anda terlihat seperti ini sebagai gantinya:

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

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

Ini gagal pada metode kelas dan kelas bersarang.
tacaswell

1

Opsi pertama adalah yang baik - dengan lekukan disertakan. Itu dalam gaya python - memberikan keterbacaan untuk kode.

Untuk menampilkannya dengan benar:

print string.lstrip()

Ini sepertinya cara termudah dan terbersih untuk memformat string kutipan tiga sehingga Anda tidak memiliki ruang ekstra karena lekukan
Taylor Liss

4
Ini hanya akan menghapus spasi di baris pertama string multiline. Itu tidak membantu dengan memformat baris berikut.
M. Schlenker

0

Tergantung bagaimana Anda ingin teks ditampilkan. Jika Anda ingin semuanya disejajarkan dengan kiri maka formatlah seperti pada cuplikan pertama atau beralih melalui garis-garis kiri-memangkas semua ruang.


5
Cara kerja alat pemrosesan docstring adalah untuk menghapus tidak semua ruang di sebelah kiri, tetapi sebanyak garis indentasi pertama. Strategi ini sedikit lebih canggih dan memungkinkan Anda untuk indentasi dan membuatnya dihormati dalam string postprocessed.
Mike Graham

0

Untuk string, Anda bisa setelah memproses string. Untuk dokumentasi Anda perlu setelah memproses fungsi sebagai gantinya. Inilah solusi untuk keduanya yang masih bisa dibaca.

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
Memproses dokumen harus sudah memproses indentasi yang konsisten, seperti dijelaskan dalam PEP 257 . Sudah ada alat - misalnya inspect.cleandoc- yang melakukan ini dengan cara yang benar.
bignose

0

Saya mengalami masalah yang sama, kode menjadi sangat tidak dapat dibaca menggunakan multilines, saya keluar dengan sesuatu seperti

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

ya, pada awalnya bisa terlihat mengerikan tetapi sintaks yang tertanam cukup kompleks dan menambahkan sesuatu di akhir (seperti '\ n "') bukan solusi


0

Anda dapat menggunakan fungsi ini 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


"""))

Hasil:

"""
line one
    line two
        line three
    line two
line one
"""
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.