Một dòng mã Python có thể biết mức lồng nhau của nó không?


149

Từ một cái gì đó như thế này:

print(get_indentation_level())

    print(get_indentation_level())

        print(get_indentation_level())

Tôi muốn có được một cái gì đó như thế này:

1
2
3

Mã có thể tự đọc theo cách này?

Tất cả những gì tôi muốn là đầu ra từ các phần lồng nhau của mã được lồng nhiều hơn. Theo cùng một cách mà điều này làm cho mã dễ đọc hơn, nó sẽ làm cho đầu ra dễ đọc hơn.

Tất nhiên tôi có thể thực hiện điều này bằng tay, bằng cách sử dụng, ví dụ .format(), nhưng điều tôi có trong đầu là một chức năng in tùy chỉnh sẽ print(i*' ' + string)imức độ thụt lề. Đây sẽ là một cách nhanh chóng để làm cho đầu ra có thể đọc được trên thiết bị đầu cuối của tôi.

Có cách nào tốt hơn để làm điều này mà tránh định dạng thủ công không?


70
Tôi thực sự tò mò về lý do tại sao bạn cần điều này.
Harrison

12
@Harrison Tôi muốn thụt đầu ra mã của mình theo cách nó được thụt vào trong mã.
Fab von Bellingshausen

14
Câu hỏi thực sự là: Tại sao bạn cần điều này? Mức thụt là tĩnh; bạn biết điều đó một cách chắc chắn khi bạn đặt số liệu get_indentation_level()vào mã của bạn. Bạn cũng có thể làm tốt print(3)hoặc bất cứ điều gì trực tiếp. Những gì có thể đáng tin cậy hơn là mức lồng nhau hiện tại trên ngăn xếp cuộc gọi hàm.
tobias_k

19
Có phải cho mục đích gỡ lỗi mã của bạn? Đây có vẻ là một cách siêu thiên tài để ghi lại luồng thực thi hoặc giống như một giải pháp siêu kỹ thuật cho một vấn đề đơn giản, và tôi không chắc đó là ... có thể cả hai!
Blackhawk

7
@FabvonBellingshausen: Nghe có vẻ như nó sẽ dễ đọc hơn rất nhiều so với bạn hy vọng. Tôi nghĩ rằng bạn có thể được phục vụ tốt hơn bằng cách chuyển một cách rõ ràng xung quanh một depththam số và thêm giá trị phù hợp vào nó khi cần thiết khi bạn chuyển nó sang các hàm khác. Việc lồng mã của bạn không có khả năng tương ứng hoàn toàn với thụt lề mà bạn muốn ra khỏi đầu ra của mình.
user2357112 hỗ trợ Monica

Câu trả lời:


115

Nếu bạn muốn thụt lề về mức độ lồng nhau hơn là khoảng trắng và tab, mọi thứ trở nên khó khăn. Ví dụ: trong đoạn mã sau:

if True:
    print(
get_nesting_level())

cuộc gọi đến get_nesting_levelthực sự được lồng sâu một cấp, mặc dù thực tế là không có khoảng trắng hàng đầu trên dòng của get_nesting_levelcuộc gọi. Trong khi đó, trong đoạn mã sau:

print(1,
      2,
      get_nesting_level())

lời kêu gọi get_nesting_levelđược lồng vào các mức 0 sâu, bất chấp sự hiện diện của khoảng trắng hàng đầu trên đường dây của nó.

Trong đoạn mã sau:

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

if True:
    print(get_nesting_level())

hai lệnh gọi get_nesting_levelở các mức lồng nhau khác nhau, mặc dù thực tế là khoảng trắng hàng đầu giống hệt nhau.

Trong đoạn mã sau:

if True: print(get_nesting_level())

đó là mức không lồng nhau, hay một? Về mặt INDENTDEDENTmã thông báo trong ngữ pháp chính thức, nó không có cấp độ sâu, nhưng bạn có thể không cảm thấy như vậy.


Nếu bạn muốn làm điều này, bạn sẽ phải mã hóa toàn bộ tệp cho đến điểm của cuộc gọi và đếm INDENTDEDENTmã thông báo. Các tokenizemô-đun sẽ rất hữu ích cho một chức năng như vậy:

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
Điều này không hoạt động theo cách tôi mong đợi khi get_nesting_level()được gọi trong hàm gọi đó, nó trả về mức lồng trong hàm đó. Nó có thể được viết lại để trả về mức lồng nhau 'toàn cầu' không?
Fab von Bellingshausen

11
@FabvonBellingshausen: Bạn có thể nhận được mức lồng nhau thụt đầu dòng và hàm gọi mức lồng nhau. Hàm này cung cấp mức lồng nhau thụt. Hàm lồng mức gọi hàm sẽ khá khác nhau và sẽ cho mức 0 cho tất cả các ví dụ của tôi. Nếu bạn muốn có một loại thụt đầu dòng / gọi tổ độ hybrid increments cho cả cuộc gọi chức năng và cơ cấu kiểm soát dòng chảy như whilewith, mà muốn được doable, nhưng nó không phải là những gì bạn yêu cầu, và thay đổi các câu hỏi để hỏi một cái gì đó khác nhau vào thời điểm này sẽ là một ý tưởng tồi
user2357112 hỗ trợ Monica

38
Ngẫu nhiên, tôi hoàn toàn đồng ý với tất cả mọi người nói rằng đây là một điều thực sự kỳ lạ. Có lẽ có một cách tốt hơn để giải quyết bất kỳ vấn đề nào bạn đang cố gắng giải quyết và việc dựa vào vấn đề này có khả năng cản trở bạn bằng cách buộc bạn sử dụng tất cả các loại hack khó chịu để tránh thay đổi cấu trúc cuộc gọi thụt lề hoặc chức năng khi bạn cần thực hiện thay đổi mã của bạn.
user2357112 hỗ trợ Monica

4
tôi chắc chắn không mong đợi ai đó đã thực sự trả lời điều này. (hãy xem xét linecachemô-đun cho những thứ như thế này - nó được sử dụng để in tracebacks và có thể xử lý các mô-đun được nhập từ tệp zip và các thủ thuật nhập kỳ lạ khác)
Eevee

6
@Eevee: Tôi chắc chắn đã không hy vọng quá nhiều người để upvote này! linecachecó thể tốt cho việc giảm số lượng tệp I / O (và cảm ơn vì đã nhắc nhở tôi về nó), nhưng nếu tôi bắt đầu tối ưu hóa điều đó, tôi sẽ bị làm phiền bởi cách chúng tôi dự phòng lại mã hóa cùng một tệp để lặp lại cùng một cuộc gọi hoặc cho nhiều trang web cuộc gọi trong cùng một tệp. Có một số cách chúng ta cũng có thể tối ưu hóa điều đó, nhưng tôi không chắc mình thực sự muốn điều chỉnh và chống đạn điều điên rồ này đến mức nào.
user2357112 hỗ trợ Monica

22

Vâng, điều đó chắc chắn có thể, đây là một ví dụ hoạt động:

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
Liên quan đến nhận xét được thực hiện bởi @Prune, điều này có thể được thực hiện để trả lại vết lõm theo cấp độ thay vì khoảng trắng không? Nó sẽ luôn ổn khi chỉ cần chia cho 4?
Fab von Bellingshausen

2
Không, chia cho 4 để nhận mức thụt lề sẽ không hoạt động với mã này. Có thể xác minh bằng cách tăng mức độ thụt lề của câu lệnh in cuối cùng, giá trị in cuối cùng chỉ tăng lên.
Craig Burgler

10
Một khởi đầu tốt, nhưng không thực sự trả lời câu hỏi imo. Số lượng không gian không giống như mức độ thụt.
wim

1
Nó không đơn giản. Thay thế 4 khoảng trắng bằng các khoảng trắng đơn có thể thay đổi logic của mã.
wim

1
Nhưng mã này là hoàn hảo cho những gì OP đang tìm kiếm: (Nhận xét của OP # 9): 'Tôi muốn thụt đầu ra mã của mình theo cách nó được thụt vào trong mã.' Vì vậy, anh ta có thể làm một cái gì đó nhưprint('{Space}'*get_indentation_level(), x)
Craig Burgler

10

Bạn có thể sử dụng sys.current_frame.f_linenođể có được số dòng. Sau đó, để tìm số cấp độ thụt đầu dòng, bạn cần tìm dòng trước đó với độ thụt bằng 0, sau đó trừ số dòng hiện tại khỏi số của dòng đó, bạn sẽ nhận được số lượng thụt dòng:

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

Bản giới thiệu:

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

Nếu bạn muốn số lượng mức thụt đầu dòng dựa trên các dòng previouse với :bạn chỉ có thể thực hiện với một chút thay đổi:

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(':'))

Bản giới thiệu:

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

Và như một câu trả lời thay thế ở đây là một chức năng để có được số lượng thụt đầu dòng (khoảng trắng):

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

câu hỏi yêu cầu số lượng mức độ thụt, không phải số lượng không gian. chúng không nhất thiết phải tỷ lệ thuận.
wim

Đối với mã demo của bạn, đầu ra phải là 1 - 2 - 3 - 3
Craig Burgler

@CraigBurgler Để có được 1 - 2 - 3 - 3, chúng ta có thể đếm số lượng dòng trước khi dòng hiện tại kết thúc bằng a :cho đến khi chúng ta bắt gặp dòng có độ thụt bằng 0, hãy xem phần chỉnh sửa!
Kasramvd

2
hmmm ... ok ... bây giờ hãy thử một số trường hợp thử nghiệm của @ user2357112;)
Craig Burgler

@CraigBurgler Giải pháp này chỉ dành cho hầu hết các trường hợp, và liên quan đến câu trả lời đó, cũng là một cách chung, nó cũng không cung cấp một giải pháp toàn diện. Hãy thử{3:4, \n 2:get_ind_num()}
Kasramvd

7

Để giải quyết vấn đề của Real real mà dẫn đến câu hỏi của bạn, bạn có thể triển khai một trình quản lý ngữ cảnh theo dõi mức độ ấn định và làm cho withcấu trúc khối trong mã tương ứng với các mức thụt đầu ra. Bằng cách này, thụt mã vẫn phản ánh thụt đầu ra mà không khớp cả hai quá nhiều. Vẫn có thể cấu trúc lại mã thành các hàm khác nhau và có các vết lõm khác dựa trên cấu trúc mã không làm rối với thụt đầu ra.

#!/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()

Đầu ra:

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
Điều này cho phép thụt vào trong không gian, không phải ở cấp độ. Trừ khi lập trình viên sử dụng số lượng thụt đầu dòng nhất quán, điều này có thể xấu khi chuyển đổi sang cấp độ.
Prune

4
Là chức năng không có giấy tờ? Tôi không thể tìm thấy nó ở đây
GingerPlusPlus
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.