บรรทัดของโค้ด Python สามารถรู้ระดับการซ้อนของย่อหน้าได้หรือไม่?


149

จากบางสิ่งเช่นนี้

print(get_indentation_level())

    print(get_indentation_level())

        print(get_indentation_level())

ฉันต้องการได้รับสิ่งนี้:

1
2
3

รหัสสามารถอ่านตัวเองด้วยวิธีนี้

สิ่งที่ฉันต้องการคือผลลัพธ์จากส่วนที่ซ้อนกันมากขึ้นของรหัสที่จะซ้อนกันมากขึ้น ในทำนองเดียวกับที่ทำให้โค้ดอ่านง่ายขึ้นก็จะทำให้อ่านง่ายขึ้น

แน่นอนฉันจะดำเนินการนี้ด้วยตนเองโดยใช้เช่น.format()แต่สิ่งที่ฉันมีในใจเป็นฟังก์ชั่นการพิมพ์ที่กำหนดเองซึ่งจะprint(i*' ' + string)ที่iเป็นระดับเยื้อง นี่จะเป็นวิธีที่รวดเร็วในการสร้างเอาต์พุตที่อ่านได้บนเทอร์มินัลของฉัน

มีวิธีที่ดีกว่าในการทำเช่นนี้ซึ่งหลีกเลี่ยงการจัดรูปแบบด้วยตนเองอย่างระมัดระวัง?


70
ฉันอยากรู้จริงๆว่าทำไมคุณถึงต้องการสิ่งนี้
แฮร์ริสัน

12
@ แฮร์ริสันฉันต้องการเยื้องการส่งออกของรหัสของฉันตามที่มันเยื้องในรหัส
Fab von Bellingshausen

14
คำถามที่แท้จริงคือ: ทำไมคุณต้องการสิ่งนี้ ระดับการเยื้องเป็นแบบคงที่ คุณรู้ได้อย่างมั่นใจเมื่อคุณใส่สถานะget_indentation_level()ลงในรหัสของคุณ คุณสามารถทำprint(3)อะไรก็ได้โดยตรง สิ่งที่น่าสนใจมากกว่าคือระดับปัจจุบันของการซ้อนบนสแตกการเรียกใช้ฟังก์ชัน
tobias_k

19
มีไว้เพื่อจุดประสงค์ในการดีบักรหัสของคุณหรือไม่ ดูเหมือนว่านี่จะเป็นวิธีที่ยอดเยี่ยมในการบันทึกการไหลของการดำเนินการหรือเป็นวิธีแก้ปัญหาที่ง่ายกว่าและเหนือกว่าสำหรับปัญหาที่เรียบง่ายและฉันไม่แน่ใจว่ามันคือ ...
Blackhawk

7
@FabvonBellingshausen: ฟังดูเหมือนว่ามันจะอ่านได้น้อยกว่าที่คุณคาดหวังมาก ฉันคิดว่าคุณอาจได้รับบริการที่ดีขึ้นโดยการส่งdepthพารามิเตอร์อย่างชัดเจนและเพิ่มมูลค่าที่เหมาะสมตามความจำเป็นเมื่อคุณส่งไปยังฟังก์ชันอื่น การซ้อนโค้ดของคุณไม่น่าจะสอดคล้องกับการเยื้องที่คุณต้องการจากเอาต์พุตของคุณ
user2357112 รองรับ Monica

คำตอบ:


115

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

if True:
    print(
get_nesting_level())

get_nesting_levelจริง ๆ แล้วการเรียกไปยังซ้อนกันลึกถึงระดับหนึ่งแม้จะมีความจริงที่ว่าไม่มีช่องว่างชั้นนำในสายของการget_nesting_levelโทร ในขณะเดียวกันในรหัสต่อไปนี้:

print(1,
      2,
      get_nesting_level())

การเรียกใช้get_nesting_levelซ้อนกันเป็นศูนย์ระดับลึกแม้จะมีช่องว่างนำหน้าในบรรทัด

ในรหัสต่อไปนี้:

if True:
  if True:
    print(get_nesting_level())

if True:
    print(get_nesting_level())

ทั้งสองเรียกให้get_nesting_levelอยู่ในระดับที่แตกต่างกันถึงแม้ว่าความจริงที่ว่าช่องว่างชั้นนำเป็นเหมือนกัน

ในรหัสต่อไปนี้:

if True: print(get_nesting_level())

ระดับศูนย์ซ้อนกันหรืออย่างใดอย่างหนึ่ง? ในแง่ของINDENTและDEDENTโทเค็นในไวยากรณ์อย่างเป็นทางการมันมีระดับลึกเป็นศูนย์ แต่คุณอาจไม่รู้สึกแบบเดียวกัน


หากคุณต้องการทำสิ่งนี้คุณจะต้องทำเครื่องหมายไฟล์ทั้งหมดจนถึงจุดที่เรียกและนับINDENTและDEDENTโทเค็น tokenizeโมดูลจะมีประโยชน์มากสำหรับการทำงานเช่น:

import inspect
import tokenize

def get_nesting_level():
    caller_frame = inspect.currentframe().f_back
    filename, caller_lineno, _, _, _ = inspect.getframeinfo(caller_frame)
    with open(filename) as f:
        indentation_level = 0
        for token_record in tokenize.generate_tokens(f.readline):
            token_type, _, (token_lineno, _), _, _ = token_record
            if token_lineno > caller_lineno:
                break
            elif token_type == tokenize.INDENT:
                indentation_level += 1
            elif token_type == tokenize.DEDENT:
                indentation_level -= 1
        return indentation_level

2
วิธีนี้ใช้ไม่ได้ในวิธีที่ฉันคาดว่าจะได้รับเมื่อget_nesting_level()มีการเรียกใช้ในฟังก์ชั่นการเรียกมันจะส่งกลับระดับการซ้อนภายในฟังก์ชันนั้น สามารถเขียนใหม่เพื่อส่งคืนระดับการซ้อน 'ทั่วโลก' ได้หรือไม่
Fab von Bellingshausen

11
@FabvonBellingshausen: คุณอาจได้รับระดับการซ้อนการเยื้องและระดับการซ้อนฟังก์ชันการเรียกฟังก์ชันรวมกัน ฟังก์ชั่นนี้จะให้ระดับการทำรังเยื้อง ระดับการซ้อนการเรียกใช้ฟังก์ชันจะค่อนข้างแตกต่างกันและจะให้ระดับ 0 สำหรับตัวอย่างทั้งหมดของฉัน หากคุณต้องการเรียงลำดับของการผสมการเยื้อง / การเรียกซ้อนไฮบริดที่เพิ่มขึ้นสำหรับทั้งการเรียกฟังก์ชั่นและโครงสร้างการไหลของการควบคุมเช่นwhileและwithที่จะทำได้ แต่ไม่ใช่สิ่งที่คุณขอและเปลี่ยนคำถามเพื่อถามสิ่งต่าง ๆ ณ จุดนี้ จะเป็นความคิดที่ไม่ดี
user2357112 รองรับ Monica

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

4
ฉันไม่ได้คาดหวังว่าจะมีคนตอบคำถามนี้จริง ๆ (พิจารณาlinecacheโมดูลสำหรับสิ่งต่าง ๆ เช่นนี้ - มันใช้เพื่อพิมพ์การติดตามย้อนกลับและสามารถจัดการโมดูลที่นำเข้าจากไฟล์ซิปและเทคนิคการนำเข้าที่แปลก ๆ )
Eevee

6
@Eevee: แน่นอนผมไม่ได้คาดหวังว่าผู้คนจำนวนมากที่จะupvoteนี้! linecacheอาจจะดีสำหรับการลดปริมาณของไฟล์ I / O (และขอขอบคุณสำหรับการเตือนฉันเกี่ยวกับมัน) แต่ถ้าฉันเริ่มเพิ่มประสิทธิภาพนั้นฉันจะต้องใส่ใจว่าเราซ้ำกันซ้ำไฟล์เดียวกันสำหรับการทำซ้ำของ การโทรเดียวกันหรือสำหรับไซต์การโทรหลายรายการภายในไฟล์เดียวกัน มีหลายวิธีที่เราสามารถปรับให้เหมาะสมเช่นกัน แต่ฉันไม่แน่ใจว่าฉันต้องการปรับแต่งและป้องกันสิ่งที่บ้าคลั่งนี้มากเพียงใด
user2357112 รองรับ Monica

22

ใช่มันเป็นไปได้อย่างแน่นอนนี่เป็นตัวอย่างการทำงาน:

import inspect

def get_indentation_level():
    callerframerecord = inspect.stack()[1]
    frame = callerframerecord[0]
    info = inspect.getframeinfo(frame)
    cc = info.code_context[0]
    return len(cc) - len(cc.lstrip())

if 1:
    print get_indentation_level()
    if 1:
        print get_indentation_level()
        if 1:
            print get_indentation_level()

4
เกี่ยวข้องกับความคิดเห็นที่ทำโดย @Prune สิ่งนี้สามารถถูกส่งคืนการเยื้องในระดับแทนที่จะเป็นช่องว่างได้หรือไม่? มันจะโอเคที่จะหารด้วย 4 เสมอไหม?
Fab von Bellingshausen

2
ไม่หารด้วย 4 เพื่อให้ได้ระดับการเยื้องจะไม่ทำงานกับรหัสนี้ สามารถตรวจสอบได้โดยเพิ่มระดับการเยื้องของคำสั่งพิมพ์ล่าสุดค่าที่พิมพ์ล่าสุดจะเพิ่มขึ้น
Craig Burgler

10
การเริ่มต้นที่ดี แต่ไม่ได้ตอบคำถามจริงๆ จำนวนช่องว่างไม่เหมือนกับระดับการเยื้อง
Wim

1
มันไม่ง่ายอย่างนั้น แทนที่ 4 ช่องว่างด้วยช่องว่างเดียวสามารถเปลี่ยนตรรกะของรหัส
Wim

1
แต่รหัสนี้เหมาะสำหรับสิ่งที่ OP มองหา: (ความคิดเห็น OP # 9): 'ฉันต้องการเยื้องผลลัพธ์ของรหัสของฉันตามวิธีการเยื้องในรหัส' เพื่อให้เขาสามารถทำสิ่งที่ต้องการprint('{Space}'*get_indentation_level(), x)
Craig Burgler

10

คุณสามารถใช้sys.current_frame.f_linenoเพื่อรับหมายเลขบรรทัด จากนั้นเพื่อหาจำนวนระดับการเยื้องคุณต้องค้นหาบรรทัดก่อนหน้าด้วยการเยื้องศูนย์จากนั้นทำการลบหมายเลขบรรทัดปัจจุบันจากหมายเลขของบรรทัดนั้นคุณจะได้จำนวนการเยื้อง:

import sys
current_frame = sys._getframe(0)

def get_ind_num():
    with open(__file__) as f:
        lines = f.readlines()
    current_line_no = current_frame.f_lineno
    to_current = lines[:current_line_no]
    previous_zoro_ind = len(to_current) - next(i for i, line in enumerate(to_current[::-1]) if not line[0].isspace())
    return current_line_no - previous_zoro_ind

การสาธิต:

if True:
    print get_ind_num()
    if True:
        print(get_ind_num())
        if True:
            print(get_ind_num())
            if True: print(get_ind_num())
# Output
1
3
5
6

หากคุณต้องการจำนวนระดับการเยื้องตามบรรทัด previouse ด้วย:คุณสามารถทำได้โดยการเปลี่ยนแปลงเล็กน้อย:

def get_ind_num():
    with open(__file__) as f:
        lines = f.readlines()

    current_line_no = current_frame.f_lineno
    to_current = lines[:current_line_no]
    previous_zoro_ind = len(to_current) - next(i for i, line in enumerate(to_current[::-1]) if not line[0].isspace())
    return sum(1 for line in lines[previous_zoro_ind-1:current_line_no] if line.strip().endswith(':'))

การสาธิต:

if True:
    print get_ind_num()
    if True:
        print(get_ind_num())
        if True:
            print(get_ind_num())
            if True: print(get_ind_num())
# Output
1
2
3
3

และเป็นคำตอบทางเลือกที่นี่เป็นฟังก์ชันสำหรับรับจำนวนการเยื้อง (ช่องว่าง):

import sys
from itertools import takewhile
current_frame = sys._getframe(0)

def get_ind_num():
    with open(__file__) as f:
        lines = f.readlines()
    return sum(1 for _ in takewhile(str.isspace, lines[current_frame.f_lineno - 1]))

คำถามที่ถามจำนวนระดับการเยื้องไม่ใช่จำนวนช่องว่าง ไม่จำเป็นต้องเป็นสัดส่วน
Wim

สำหรับโค้ดตัวอย่างของคุณผลลัพธ์ควรเป็น 1 - 2 - 3 - 3
Craig Burgler

@CraigBurgler สำหรับการรับ 1 - 2 - 3 - 3 เราสามารถนับจำนวนบรรทัดก่อนที่บรรทัดปัจจุบันที่ลงท้ายด้วย a :จนกว่าเราจะพบบรรทัดที่มีการเยื้องศูนย์ตรวจสอบการแก้ไข!
Kasramvd

2
อืม ... โอเค ... ตอนนี้ลองใช้กรณีทดสอบของ @ user2357112;)
Craig Burgler

@CraigBurgler วิธีนี้เป็นเพียงกรณีส่วนใหญ่และเกี่ยวกับคำตอบนั้นก็เป็นวิธีทั่วไปก็ไม่ได้ให้โซลูชันที่ครอบคลุม ลอง{3:4, \n 2:get_ind_num()}
Kasramvd

7

ในการแก้ปัญหา "ของจริง" ที่นำไปสู่คำถามของคุณคุณสามารถใช้ตัวจัดการบริบทซึ่งติดตามระดับการเยื้องและทำให้withโครงสร้างบล็อกในรหัสสอดคล้องกับระดับการเยื้องของเอาต์พุต วิธีนี้เยื้องรหัสยังคงสะท้อนถึงการเยื้องเอาต์พุตโดยไม่ต้องเชื่อมต่อทั้งสองมากเกินไป ยังคงเป็นไปได้ที่จะ refactor รหัสลงในฟังก์ชั่นที่แตกต่างกันและมีการเยื้องอื่น ๆ ขึ้นอยู่กับโครงสร้างรหัสที่ไม่ยุ่งกับการเยื้องเอาท์พุท

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function


class IndentedPrinter(object):

    def __init__(self, level=0, indent_with='  '):
        self.level = level
        self.indent_with = indent_with

    def __enter__(self):
        self.level += 1
        return self

    def __exit__(self, *_args):
        self.level -= 1

    def print(self, arg='', *args, **kwargs):
        print(self.indent_with * self.level + str(arg), *args, **kwargs)


def main():
    indented = IndentedPrinter()
    indented.print(indented.level)
    with indented:
        indented.print(indented.level)
        with indented:
            indented.print('Hallo', indented.level)
            with indented:
                indented.print(indented.level)
            indented.print('and back one level', indented.level)


if __name__ == '__main__':
    main()

เอาท์พุท:

0
  1
    Hallo 2
      3
    and back one level 2

6
>>> import inspect
>>> help(inspect.indentsize)
Help on function indentsize in module inspect:

indentsize(line)
    Return the indent size, in spaces, at the start of a line of text.

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

4
ฟังก์ชั่นนี้ไม่มีเอกสารหรือไม่? ฉันหาไม่พบที่นี่
GingerPlusPlus
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.