เยื้องที่เหมาะสมสำหรับ Python หลายสาย


456

การเยื้องที่เหมาะสมสำหรับ Python หลายสายภายในฟังก์ชันคืออะไร

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

หรือ

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

หรืออย่างอื่น?

มันดูแปลก ๆ ที่มีสตริงที่ห้อยอยู่ภายนอกฟังก์ชั่นในตัวอย่างแรก


4
Docstrings ได้รับการปฏิบัติเป็นพิเศษ : การเยื้องของบรรทัดแรกจะถูกลบออก เยื้องทั่วไปที่เล็กที่สุดที่ถูกยึดเหนือบรรทัดที่ไม่ว่างอื่น ๆ ทั้งหมดจะถูกลบออกจากพวกเขาทั้งหมด นอกเหนือจากนั้นตัวอักษรสตริงหลายบรรทัดใน Python น่าเสียดายที่สิ่งที่คุณเห็นคือสิ่งที่คุณได้รับในแง่ของช่องว่าง: ตัวละครทั้งหมดระหว่างตัวคั่นสตริงกลายเป็นส่วนหนึ่งของสตริงรวมถึงการเยื้องว่าด้วย Python อ่านสัญชาตญาณ ดูเหมือนว่าควรจะวัดจากการเยื้องของเส้นที่ตัวอักษรเริ่มต้น
Evgeni Sergeev

@EvgeniSergeev เครื่องมือการประมวลผลทำงานนี้ (และส่วนใหญ่ขึ้นอยู่กับการเลือกเครื่องมือการประมวลผลของคุณ) method.__doc__Python ไม่ได้ทำการแก้ไขใด ๆ มากกว่าstrตัวอักษรอื่น ๆ
cz

คำตอบ:


453

คุณอาจต้องการที่จะสอดคล้องกับ """

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

เนื่องจากการขึ้นบรรทัดใหม่และช่องว่างรวมอยู่ในสตริงนั้นคุณจะต้องทำการประมวลผลภายหลัง หากคุณไม่ต้องการทำเช่นนั้นและมีข้อความจำนวนมากคุณอาจต้องการเก็บแยกไว้ในไฟล์ข้อความ หากไฟล์ข้อความทำงานได้ไม่ดีสำหรับแอปพลิเคชันของคุณและคุณไม่ต้องการประมวลผลภายหลังฉันอาจไปด้วย

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

หากคุณต้องการประมวลผลสตริงหลายบรรทัดเพื่อตัดส่วนที่ไม่ต้องการออกคุณควรพิจารณาtextwrapโมดูลหรือเทคนิคสำหรับการประมวลผลเอกสารหลังการประมวลผลที่แสดงใน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
นี่คือรูปแบบ 'การเยื้องที่แขวนอยู่' ของความต่อเนื่องของเส้น มันถูกกำหนดไว้ใน PEP8 สำหรับวัตถุประสงค์เช่นคำจำกัดความของฟังก์ชั่นและคำสั่งยาวหากไม่ได้กล่าวถึงสำหรับสตริงหลายบรรทัด โดยส่วนตัวนี่เป็นที่แห่งเดียวที่ฉันปฏิเสธที่จะติดตาม PEP8 (และใช้การเว้นวรรค 4 ช่องแทน) เนื่องจากฉันไม่ชอบเยื้องแขวนอย่างรุนแรงซึ่งสำหรับฉันปิดบังโครงสร้างที่เหมาะสมของโปรแกรม
bobince

2
@buffer ใน 3.1.2 ของบทช่วยสอนอย่างเป็นทางการ ("ตัวอักษรสองตัวที่อยู่ติดกันจะถูกต่อกันโดยอัตโนมัติ ... ") และในการอ้างอิงภาษา
Mike Graham

5
แบบฟอร์มที่สองที่มีการต่อสตริงอัตโนมัติไม่รวมบรรทัดใหม่เป็นคุณลักษณะ
Mike Graham

19
trim()ฟังก์ชั่นตามที่ระบุใน PEP257 inspect.cleandocจะดำเนินการในห้องสมุดมาตรฐาน

2
+1 ถึง @bobince ความคิดเห็นเกี่ยวกับการปฏิเสธ "การเยื้องเยื้อง" ที่นี่ ... โดยเฉพาะอย่างยิ่งเพราะถ้าคุณเปลี่ยนชื่อตัวแปรจากstringเป็นtextหรืออะไรก็ตามที่มีความยาวต่างกันตอนนี้คุณต้องอัปเดตการเยื้องของบรรทัดเดียวทุกบรรทัดของ สตริง multilineเพียงเพื่อให้ตรงกับที่"""เหมาะสม กลยุทธ์การเยื้องไม่ควรทำให้ซับซ้อน refactors / การบำรุงรักษาในอนาคตและเป็นหนึ่งในสถานที่ที่ PEP ล้มเหลวจริงๆ
kevlarr

255

textwrap.dedentฟังก์ชั่นช่วยให้หนึ่งในการเริ่มต้นด้วยการเยื้องที่ถูกต้องในแหล่งที่มาและจากนั้นตัดมันออกจากข้อความก่อนการใช้งาน

การแลกเปลี่ยนตามที่ระบุไว้โดยคนอื่น ๆ ก็คือว่านี่คือการเรียกฟังก์ชั่นพิเศษเกี่ยวกับตัวอักษร; พิจารณาสิ่งนี้เมื่อตัดสินใจว่าจะวางตัวอักษรเหล่านี้ในรหัสของคุณที่ใด

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

2
@haridsv ต้นกำเนิดของภัยพิบัติ / ความไร้ประสิทธิภาพนั้นได้นิยามสตริงคงที่ภายในฟังก์ชันที่เรียกว่าเป็นประจำ เป็นไปได้ที่จะแลกเปลี่ยนคำจำกัดความคงที่ต่อการโทรสำหรับการค้นหาต่อการโทร วิธีการที่dedent preprocessing จะวิ่งได้เพียงครั้งเดียว คำถามที่เกี่ยวข้องอาจเป็นstackoverflow.com/q/15495376/611007มันแสดงรายการแนวคิดเพื่อหลีกเลี่ยงการกำหนดค่าคงที่ต่อการโทรแต่ละครั้ง ดูเหมือนว่าทางเลือกจะต้องมีการค้นหา ยังคงมีหลายวิธีในการค้นหาสถานที่ที่น่าใช้ในการจัดเก็บ ตัวอย่างเช่นบรรทัดถัดไปแล้วdef foo: return foo.x foo.x = textwrap.dedent("bar")
n611x007

1
ฉันเดาว่าจะไม่มีประสิทธิภาพหากสตริงมีไว้สำหรับการบันทึกที่เปิดใช้งานเฉพาะในโหมดแก้ไขข้อบกพร่องและไม่ได้ใช้งานเป็นอย่างอื่น แต่ทำไมบันทึกสตริงหลายบรรทัดตัวอักษรอยู่แล้ว? ดังนั้นจึงเป็นเรื่องยากที่จะหาตัวอย่างในชีวิตจริงที่ข้างต้นจะไม่มีประสิทธิภาพ (เช่นที่มันทำให้โปรแกรมช้าลงอย่างมาก) เพราะอะไรก็ตามที่ใช้สายเหล่านี้จะช้าลง
Evgeni Sergeev

53

ใช้inspect.cleandocอย่างนั้น:

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

เยื้องสัมพัทธ์จะถูกเก็บรักษาไว้ตามที่คาดไว้ ในฐานะที่เป็นความเห็นtextwrap.dedentด้านล่างหากคุณต้องการที่จะเก็บก่อนบรรทัดว่างใช้ อย่างไรก็ตามนั่นก็ยังทำให้ตัวแบ่งบรรทัดแรก

หมายเหตุ:เป็นการดีที่จะเยื้องบล็อกโลจิคัลของโค้ดภายใต้บริบทที่เกี่ยวข้องเพื่อทำให้โครงสร้างชัดเจนขึ้น stringเช่นสตริงหลายสายที่อยู่ในตัวแปร


5
สับสนทำไมคำตอบนี้ไม่ได้มีอยู่จนถึงขณะนี้inspect.cleandocมีมาตั้งแต่Python 2.6ซึ่งเป็น2008 .. ? คำตอบที่สะอาดที่สุดอย่างแน่นอนโดยเฉพาะอย่างยิ่งเพราะมันไม่ได้ใช้สไตล์การเยื้องที่แขวนอยู่ซึ่งทำให้เปลืองพื้นที่โดยไม่จำเป็นออกไป
kevlarr

1
วิธีนี้จะลบข้อความว่างสองสามบรรทัดแรก (ถ้ามี) หากคุณไม่ต้องการพฤติกรรมดังกล่าวให้ใช้ textwrap.dedent docs.python.org/2/library/textwrap.html#textwrap.dedent
joshuakcockrell

1
สมบูรณ์แบบ!
zzzz zzzz

23

ตัวเลือกหนึ่งที่ดูเหมือนว่าจะหายไปจากคำตอบอื่น ๆ (เฉพาะที่กล่าวถึงอย่างลึกลงไปในความคิดเห็นโดย naxa) คือต่อไปนี้:

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

สิ่งนี้จะช่วยให้การจัดตำแหน่งที่เหมาะสมเข้าร่วมบรรทัดโดยปริยายและยังคงให้การเปลี่ยนบรรทัดซึ่งสำหรับฉันเป็นหนึ่งในเหตุผลที่ฉันต้องการใช้สตริงหลายบรรทัดอย่างไรก็ตาม

ไม่จำเป็นต้องมีการประมวลผลภายหลัง แต่คุณต้องเพิ่มด้วยตนเอง\nณ ที่ใดก็ตามที่คุณต้องการให้บรรทัดสิ้นสุด ทั้งอินไลน์หรือเป็นสตริงแยกต่างหากหลังจาก หลังนี้ง่ายต่อการคัดลอกวาง


โปรดทราบว่านี่เป็นตัวอย่างของสตริงที่เข้าร่วมโดยปริยายไม่ใช่สตริงหลายบรรทัด
trk

@trk มันเป็นหลายบรรทัดในแง่ที่สตริงมีการขึ้นบรรทัดใหม่ (aka หลายบรรทัด) แต่ใช่มันใช้การเข้าร่วมเพื่อหลีกเลี่ยงปัญหาการจัดรูปแบบที่ OP มี
holroy

17

ตัวเลือกเพิ่มเติม ใน Ipython ที่เปิดใช้งาน pylab แล้วผู้อุทิศจะอยู่ในเนมสเปซแล้ว ฉันตรวจสอบและมาจาก 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
"""

การใช้ matplotlib ที่อุทิศให้กับตัวอย่างทั้งสามนี้จะให้ผลลัพธ์ที่เหมือนกัน ฟังก์ชั่นการอุทิศ textwrap จะมีบรรทัดว่างนำหน้าด้วยตัวอย่างที่ 1

ข้อเสียที่เห็นได้ชัดคือ textwrap อยู่ในไลบรารีมาตรฐานในขณะที่ matplotlib เป็นโมดูลภายนอก

การแลกเปลี่ยนบางอย่างที่นี่ ... ฟังก์ชั่นพิเศษทำให้โค้ดของคุณสามารถอ่านได้มากขึ้นเมื่อมีการกำหนดสตริง แต่ต้องการการประมวลผลในภายหลังเพื่อรับสตริงในรูปแบบที่ใช้งานได้ ในเอกสารมีความชัดเจนว่าคุณควรใช้การเยื้องที่ถูกต้องเนื่องจากการใช้งานส่วนใหญ่ของ docstring จะทำการประมวลผลที่จำเป็น

เมื่อฉันต้องการสตริงที่ไม่ยาวในรหัสของฉันฉันพบรหัสน่าเกลียดดังต่อไปนี้ที่ฉันปล่อยให้สตริงที่ยาวหลุดออกจากการเยื้อง ล้มเหลวอย่างแน่นอนใน "ความสวยงามดีกว่าน่าเกลียด" แต่ใคร ๆ ก็สามารถแย้งได้ว่ามันง่ายกว่าและชัดเจนกว่าทางเลือกที่อุทิศตน

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

แม้ว่านี่จะไม่ใช่วิธีที่ดีที่สุด แต่ฉันก็ใช้เป็นครั้งคราว ถ้าคุณทำใช้มันคุณควรใช้ tuple แทนของรายการเพราะมันจะไม่ได้รับการแก้ไขก่อนที่จะถูกเข้าร่วม
Lyndsy Simon

4

ฉันชอบ

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

หรือ

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

1
สิ่งนี้ไม่ได้ตอบคำถามเพราะคำถามระบุอย่างชัดเจนว่าการเยื้อง (ภายในฟังก์ชัน) มีความสำคัญ
bignose

@bignose คำถามที่กล่าวว่า "มันดูแปลก ๆ " ไม่อนุญาตให้ใช้
lk_vc

ฉันจะทำสิ่งนี้ให้สำเร็จได้อย่างไรหากไม่มีการเยื้องที่น่าเกลียด?
lfender6445

@ lfender6445 ดีบางทีคุณอาจจะวางสตริงทั้งหมดเหล่านี้เป็นไฟล์แยกจากรหัสอื่น ๆ ...
lk_vc

3

สองเซ็นต์ของฉันหนีออกจากจุดสิ้นสุดเพื่อรับการเยื้อง:

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

1

ฉันมาที่นี่เพื่อค้นหา 1-liner ง่าย ๆ เพื่อลบ / แก้ไขระดับการระบุของ docstring สำหรับการพิมพ์โดยไม่ทำให้ดูสกปรกเช่นโดยการทำให้ "อยู่นอกฟังก์ชัน" ภายในสคริปต์

นี่คือสิ่งที่ฉันทำ:

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

และคุณไม่จำเป็นต้องลบอักขระแรกหากคุณต้องการให้เอกสารของคุณมีหน้าตาแบบนี้แทน:

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

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

สิ่งนี้ล้มเหลวในวิธีการเรียนและชั้นเรียนที่ซ้อนกัน
tacaswell

1

ตัวเลือกแรกคือตัวเลือกที่ดี - รวมการเยื้อง มันอยู่ในรูปแบบหลาม - ให้อ่านง่ายสำหรับรหัส

วิธีแสดงอย่างถูกต้อง:

print string.lstrip()

ดูเหมือนว่าวิธีที่ง่ายและสะอาดที่สุดในการจัดรูปแบบสตริงคำพูดสามคำดังนั้นคุณไม่มีที่ว่างเพิ่มเติมเนื่องจากการเยื้อง
Taylor Liss

4
สิ่งนี้จะลบช่องว่างนำหน้าในบรรทัดแรกของสตริงหลายบรรทัด มันไม่ได้ช่วยในการจัดรูปแบบบรรทัดต่อไปนี้
เอ็ม Schlenker

0

ขึ้นอยู่กับว่าคุณต้องการให้ข้อความแสดงผลอย่างไร หากคุณต้องการให้จัดแนวซ้ายทั้งหมดให้จัดรูปแบบเหมือนในตัวอย่างแรกหรือวนซ้ำตามบรรทัดที่เหลือไว้เพื่อตัดพื้นที่ทั้งหมด


5
วิธีการทำงานของเครื่องมือการประมวลผล docstring คือการลบพื้นที่ทั้งหมดทางด้านซ้ายไม่ใช่ แต่จะเท่ากับบรรทัดแรกที่เยื้องเข้าไป กลยุทธ์นี้มีความซับซ้อนมากขึ้นและช่วยให้คุณสามารถเยื้องและให้ความเคารพในสตริงที่ประมวลผลแล้ว
Mike Graham

0

สำหรับสตริงที่คุณสามารถประมวลผลสตริงได้ สำหรับเอกสารคุณต้องดำเนินการฟังก์ชั่นแทน นี่คือวิธีแก้ปัญหาสำหรับทั้งสองที่ยังคงสามารถอ่านได้

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
docstrings การประมวลผลแล้วจะต้องดำเนินการเยื้องสอดคล้องตามที่อธิบายไว้ใน PEP 257 มีเครื่องมืออยู่แล้ว - เช่นinspect.cleandoc- ซึ่งทำสิ่งนี้อย่างถูกวิธี
bignose

0

ฉันมีปัญหาที่คล้ายกันรหัสไม่สามารถอ่านได้โดยใช้ multilines ฉันออกมาพร้อมกับสิ่งที่ชอบ

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.