Lõm đúng cách cho chuỗi multiline Python


456

Sự thụt lề thích hợp cho các chuỗi multiline Python trong một hàm là gì?

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

hoặc là

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

hay cái gì khác?

Nó trông có vẻ kỳ lạ khi có chuỗi treo bên ngoài hàm trong ví dụ đầu tiên.


4
Tài liệu được xử lý đặc biệt : mọi vết lõm của dòng đầu tiên được loại bỏ; thụt lề chung nhỏ nhất được thực hiện trên tất cả các dòng không trống khác được xóa khỏi tất cả các dòng. Ngoài ra, các chuỗi ký tự nhiều dòng trong Python không may là những gì bạn thấy là gì về khoảng trắng: tất cả các ký tự giữa các dấu phân cách chuỗi đều trở thành một phần của chuỗi, bao gồm cả thụt lề, với bản năng đọc Python, có vẻ như nó nên được đo từ chỗ thụt dòng bắt đầu bằng chữ.
Evgeni Sergeev

@EvgeniSergeev Công cụ xử lý thực hiện nhiệm vụ này (và điều đó phần lớn phụ thuộc vào sự lựa chọn công cụ xử lý của bạn). method.__doc__không được sửa đổi bởi chính Python hơn bất kỳ strnghĩa đen nào khác .
cz

Câu trả lời:


453

Bạn có thể muốn xếp hàng với """

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

Vì các dòng mới và khoảng trắng được bao gồm trong chính chuỗi, bạn sẽ phải xử lý nó. Nếu bạn không muốn làm điều đó và bạn có rất nhiều văn bản, bạn có thể muốn lưu trữ nó một cách riêng biệt trong một tệp văn bản. Nếu một tệp văn bản không hoạt động tốt cho ứng dụng của bạn và bạn không muốn xử lý hậu kỳ, có lẽ tôi sẽ đi cùng

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

Nếu bạn muốn xử lý một chuỗi nhiều dòng để cắt bớt các phần bạn không cần, bạn nên xem xét textwrapmô-đun hoặc kỹ thuật cho các tài liệu hậu xử lý được trình bày trong 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
Đây là kiểu nối tiếp 'treo thụt'. Nó được quy định trong PEP8 cho các mục đích như định nghĩa hàm và các câu lệnh if dài, mặc dù không được đề cập cho các chuỗi đa dòng. Cá nhân đây là một nơi tôi từ chối theo dõi PEP8 (và sử dụng thụt lề 4 không gian thay thế), vì tôi không thích thụt lề treo, điều này đối với tôi che khuất cấu trúc phù hợp của chương trình.
bobince

2
@buffer, trong 3.1.2 của hướng dẫn chính thức ("Hai chuỗi ký tự nằm cạnh nhau được tự động nối ...") và trong tài liệu tham khảo ngôn ngữ.
Mike Graham

5
Hình thức thứ hai với nối chuỗi tự động không bao gồm dòng mới Đây là một tính năng.
Mike Graham

19
Các trim()chức năng theo quy định tại PEP257 được thực hiện trong các thư viện chuẩn như inspect.cleandoc.

2
Nhận xét của +1 đối với @bobince về việc từ chối "thụt lề treo" ở đây ... Đặc biệt bởi vì nếu bạn thay đổi tên biến stringthành texthoặc bất kỳ chiều dài nào khác, thì bây giờ bạn cần cập nhật thụt lề theo từng dòng của từng dòng chuỗi multiline chỉ để làm cho nó phù hợp với """đúng. Chiến lược thụt lề không nên làm phức tạp các nhà tái cấu trúc / bảo trì trong tương lai và đó là một trong những nơi mà PEP thực sự thất bại
kevlarr

254

Các textwrap.dedentchức năng cho phép một để bắt đầu với thụt đầu dòng đúng trong nguồn , và sau đó tách nó từ các văn bản trước khi sử dụng.

Sự đánh đổi, như một số người khác lưu ý, là đây là một chức năng bổ sung gọi theo nghĩa đen; hãy tính đến điều này khi quyết định nơi đặt những chữ này trong mã của bạn.

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)

Việc theo dõi \trong thông điệp nhật ký bằng chữ là để đảm bảo rằng ngắt dòng không theo nghĩa đen; theo cách đó, nghĩa đen không bắt đầu bằng một dòng trống, và thay vào đó bắt đầu với dòng đầy đủ tiếp theo.

Giá trị trả về từ textwrap.dedentlà chuỗi đầu vào với tất cả các vết lõm khoảng trắng hàng đầu phổ biến được loại bỏ trên mỗi dòng của chuỗi. Vì vậy, log_messagegiá trị trên sẽ là:

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

1
Mặc dù đây là một giải pháp hợp lý và rất tốt để biết, làm một việc như thế này bên trong một chức năng được gọi thường xuyên có thể chứng minh là một thảm họa.
haridsv

@haridsv Tại sao đó sẽ là một thảm họa?
jtmoulia

10
@jtmoulia: Một mô tả tốt hơn thảm họa sẽ là "không hiệu quả" vì kết quả của textwrap.dedent()cuộc gọi là một giá trị không đổi, giống như đối số đầu vào của nó.
martineau

2
@haridsv nguồn gốc của thảm họa / kém hiệu quả đó là xác định một chuỗi không đổi bên trong một hàm thường được gọi. Có thể giao dịch định nghĩa hằng số cho mỗi cuộc gọi để tra cứu mỗi cuộc gọi. Bằng cách đó, quá trình tiền xử lý chỉ sẽ chạy một lần . Một câu hỏi có liên quan có thể là stackoverflow.com/q/15495376/611007 Nó liệt kê các ý tưởng để tránh xác định hằng số cho mỗi cuộc gọi. Mặc dù các lựa chọn thay thế dường như đòi hỏi phải tra cứu. Tuy nhiên, nhiều cách khác nhau để tìm nơi thuận lợi để lưu trữ nó đã được cố gắng. Ví dụ: def foo: return foo.xdòng tiếp theo foo.x = textwrap.dedent("bar").
n611x007

1
Tôi đoán nó sẽ không hiệu quả nếu chuỗi được dùng để ghi nhật ký chỉ được kích hoạt trong chế độ gỡ lỗi và không được sử dụng theo cách khác. Nhưng sau đó tại sao đăng nhập một chuỗi nhiều chữ bằng cách nào? Vì vậy, thật khó để tìm thấy một ví dụ thực tế trong đó phần trên sẽ không hiệu quả (nghĩa là nó làm chậm chương trình một cách đáng kể), bởi vì bất cứ điều gì tiêu thụ các chuỗi này sẽ chậm hơn.
Evgeni Sergeev

52

Sử dụng inspect.cleandocnhư vậy:

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

Lõm tương đối sẽ được duy trì như mong đợi. Như đã nhận xét dưới đây, nếu bạn muốn giữ các dòng trống trước, hãy sử dụng textwrap.dedent. Tuy nhiên, điều đó cũng giữ cho dòng đầu tiên bị phá vỡ.

Lưu ý: Đó là cách thực hành tốt để thụt các khối mã logic trong ngữ cảnh liên quan của nó để làm rõ cấu trúc. Ví dụ: chuỗi nhiều dòng thuộc về biến string.


5
Quá bối rối tại sao câu trả lời này không tồn tại cho đến bây giờ, inspect.cleandocđã tồn tại kể từ Python 2.6 , đó là năm 2008 ..? Câu trả lời hoàn toàn rõ ràng nhất, đặc biệt là vì nó không sử dụng kiểu thụt lề treo, điều này chỉ làm lãng phí một không gian không cần thiết
kevlarr

1
Giải pháp này sẽ loại bỏ một vài dòng đầu tiên của văn bản trống (nếu có). Nếu bạn không muốn điều đó hành vi, sử dụng textwrap.dedent docs.python.org/2/library/textwrap.html#textwrap.dedent
joshuakcockrell

1
Đây là hoàn hảo!
zzzz zzzz

23

Một tùy chọn dường như bị thiếu trong các câu trả lời khác (chỉ được đề cập sâu trong một nhận xét của naxa) là:

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

Điều này sẽ cho phép căn chỉnh phù hợp, tham gia các dòng hoàn toàn và vẫn giữ sự thay đổi dòng, mà đối với tôi, đó là một trong những lý do tại sao tôi muốn sử dụng chuỗi đa dòng.

Nó không yêu cầu bất kỳ quá trình hậu xử lý nào, nhưng bạn cần thêm thủ công \ntại bất kỳ địa điểm cụ thể nào mà bạn muốn dòng kết thúc. Hoặc là nội tuyến hoặc là một chuỗi riêng biệt sau. Cái sau dễ dàng hơn để sao chép-dán vào.


Lưu ý rằng đây là một ví dụ về một chuỗi được nối ngầm, không phải là một chuỗi nhiều dòng.
trk

@trk, đó là multiline theo nghĩa là chuỗi chứa dòng mới (còn gọi là nhiều dòng), nhưng vâng, nó sử dụng phép nối để tránh các vấn đề định dạng mà OP gặp phải.
holroy

17

Một số tùy chọn khác. Trong Ipython với pylab được kích hoạt, cống hiến đã có trong không gian tên. Tôi đã kiểm tra và nó là từ matplotlib. Hoặc nó có thể được nhập với:

from matplotlib.cbook import dedent

Trong tài liệu có ghi rằng nó nhanh hơn so với textwrap tương đương và trong các thử nghiệm của tôi trong ipython, nó thực sự nhanh hơn trung bình 3 lần so với các thử nghiệm nhanh của tôi. Nó cũng có lợi ích là nó loại bỏ bất kỳ dòng trống hàng đầu nào, điều này cho phép bạn linh hoạt trong cách bạn xây dựng chuỗi:

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

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

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

Sử dụng suy luận matplotlib trên ba ví dụ này sẽ cho kết quả hợp lý như nhau. Hàm suy luận textwrap sẽ có một dòng trống hàng đầu với ví dụ thứ nhất.

Bất lợi rõ ràng là textwrap nằm trong thư viện tiêu chuẩn trong khi matplotlib là mô-đun bên ngoài.

Một số sự đánh đổi ở đây ... các hàm suy diễn làm cho mã của bạn dễ đọc hơn khi các chuỗi được xác định, nhưng yêu cầu xử lý sau để có được chuỗi ở định dạng có thể sử dụng được. Trong tài liệu, rõ ràng là bạn nên sử dụng thụt lề chính xác vì hầu hết việc sử dụng chuỗi doc sẽ thực hiện xử lý theo yêu cầu.

Khi tôi cần một chuỗi không dài trong mã của mình, tôi tìm thấy mã xấu được thừa nhận sau đây khi tôi để chuỗi dài thoát ra khỏi vết lõm kèm theo. Chắc chắn thất bại trong "Đẹp thì tốt hơn xấu.", Nhưng người ta có thể lập luận rằng nó đơn giản và rõ ràng hơn so với sự thay thế hoàn hảo.

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

Nếu bạn muốn một giải pháp nhanh chóng và dễ dàng và tự cứu mình khỏi việc nhập các dòng mới, bạn có thể chọn một danh sách thay thế, ví dụ:

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

Mặc dù đây không phải là cách tiếp cận tốt nhất, thỉnh thoảng tôi vẫn sử dụng nó. Nếu bạn làm sử dụng nó, bạn nên sử dụng một tuple thay vì một danh sách, vì nó sẽ không được sửa đổi trước khi được gia nhập.
Lyndsy Simon

4

tôi thích

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

hoặc là

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

1
Điều này không trả lời câu hỏi, bởi vì câu hỏi nói rõ rằng vết lõm (trong hàm) có vấn đề.
bignose

@bignose Câu hỏi cho biết "Trông có vẻ kỳ lạ" không được phép sử dụng.
lk_vc

Làm thế nào tôi có thể thực hiện điều này mà không có vết lõm xấu xí?
lfender6445

@ lfender6445 tốt, có lẽ bạn có thể đặt tất cả các chuỗi này vào một tệp riêng biệt từ các mã khác ...
lk_vc

3

Hai xu của tôi, thoát khỏi cuối dòng để có được các vết lõm:

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

1

Tôi đến đây để tìm kiếm một lớp lót đơn giản để loại bỏ / sửa mức độ nhận dạng của chuỗi doc để in, mà không làm cho nó trông không gọn gàng , ví dụ bằng cách làm cho nó "treo bên ngoài chức năng" trong tập lệnh.

Đây là những gì tôi đã làm:

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

Rõ ràng, nếu bạn thụt lề bằng dấu cách (ví dụ 4) thay vì phím tab, hãy sử dụng cái gì đó như thế này:

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

Và bạn không cần phải xóa ký tự đầu tiên nếu bạn muốn các tài liệu của mình trông giống như thế này:

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

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

Điều này thất bại trên các phương thức lớp và các lớp lồng nhau.
tacaswell

1

Tùy chọn đầu tiên là lựa chọn tốt - bao gồm thụt đầu dòng. Đó là trong phong cách python - cung cấp khả năng đọc cho mã.

Để hiển thị đúng:

print string.lstrip()

Đây có vẻ là cách đơn giản và gọn gàng nhất để định dạng ba chuỗi trích dẫn để bạn không có thêm khoảng trắng do thụt lề
Taylor Liss

4
Điều này sẽ chỉ xóa các khoảng trắng hàng đầu trong dòng đầu tiên của chuỗi nhiều dòng. Nó không giúp định dạng các dòng sau.
M. Schlenker

0

Nó phụ thuộc vào cách bạn muốn văn bản hiển thị. Nếu bạn muốn tất cả được căn lề trái thì hãy định dạng nó như trong đoạn trích đầu tiên hoặc lặp qua các dòng bên trái - cắt xén tất cả khoảng trắng.


5
Cách thức hoạt động của các công cụ xử lý chuỗi là loại bỏ không phải tất cả khoảng trống ở bên trái, mà nhiều như dòng thụt đầu tiên. Chiến lược này phức tạp hơn một chút và cho phép bạn thụt lề và nó được tôn trọng trong chuỗi xử lý sau.
Mike Graham

0

Đối với chuỗi bạn có thể chỉ sau khi xử lý chuỗi. Đối với tài liệu bạn cần sau khi xử lý chức năng thay thế. Đây là một giải pháp cho cả hai vẫn có thể đọc được.

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
Xử lý các tài liệu phải xử lý thụt đầu dòng nhất quán, như được mô tả trong PEP 257 . Đã có các công cụ - ví dụ inspect.cleandoc- làm điều này đúng cách.
bignose

0

Tôi đang gặp vấn đề tương tự, mã thực sự không thể đọc được bằng cách sử dụng đa dòng, tôi đã đưa ra một cái gì đó như

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

vâng, lúc đầu có thể trông khủng khiếp nhưng cú pháp nhúng khá phức tạp và việc thêm một cái gì đó ở cuối (như '\ n "') không phải là một giải pháp


0

Bạn có thể sử dụng chức năng này 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


"""))

Kết quả:

"""
line one
    line two
        line three
    line two
line one
"""
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.