Bisakah satu baris kode Python mengetahui tingkat lekukan lekukannya?

149

Dari sesuatu seperti ini:

print(get_indentation_level())

    print(get_indentation_level())

        print(get_indentation_level())

Saya ingin mendapatkan sesuatu seperti ini:

1
2
3

Bisakah kode membaca sendiri dengan cara ini?

Yang saya inginkan adalah output dari bagian kode yang lebih bersarang menjadi lebih bersarang. Dengan cara yang sama ini membuat kode lebih mudah dibaca, itu akan membuat output lebih mudah dibaca.

Tentu saja saya bisa mengimplementasikan ini secara manual, menggunakan eg .format(), tetapi yang ada dalam pikiran saya adalah fungsi cetak kustom yang akan di print(i*' ' + string)mana itingkat indentasi. Ini akan menjadi cara cepat untuk membuat output yang dapat dibaca di terminal saya.

Apakah ada cara yang lebih baik untuk melakukan ini yang menghindari pemformatan manual yang telaten?

Fab von Bellingshausen
sumber
70
Saya benar-benar ingin tahu mengapa Anda membutuhkan ini.
Harrison
12
@ Harrison Saya ingin membuat indentasi output dari kode saya sesuai dengan bagaimana indentasi dalam kode.
Fab von Bellingshausen
14
Pertanyaan sebenarnya adalah: Mengapa Anda membutuhkan ini? Level indentasi bersifat statis; Anda tahu dengan pasti ketika Anda memasukkan get_indentation_level()statment ke dalam kode Anda. Anda bisa melakukan print(3)atau apa pun secara langsung. Apa yang mungkin lebih itneresting adalah tingkat saat ini bersarang di tumpukan panggilan fungsi.
tobias_k
19
Apakah ini untuk tujuan debug kode Anda? Tampaknya ini cara super jenius dalam mencatat alur eksekusi atau seperti solusi super-rekayasa untuk masalah sederhana, dan saya tidak yakin mana itu ... mungkin keduanya!
Blackhawk
7
@FabvonBellingshausen: Kedengarannya itu akan jauh lebih mudah dibaca daripada yang Anda harapkan. Saya pikir Anda mungkin lebih baik dilayani dengan memberikan depthparameter secara eksplisit dan menambahkan nilai yang sesuai dengannya saat diperlukan saat Anda meneruskannya ke fungsi lain. Bersarangnya kode Anda kemungkinan tidak akan sesuai dengan lekukan yang Anda inginkan dari output Anda.
user2357112 mendukung Monica

Jawaban:

115

Jika Anda ingin lekukan dalam hal tingkat bersarang daripada spasi dan tab, segalanya menjadi rumit. Misalnya, dalam kode berikut:

if True:
    print(
get_nesting_level())

panggilan untuk get_nesting_levelsebenarnya bersarang sedalam satu tingkat, meskipun faktanya tidak ada spasi putih pada saluran get_nesting_levelpanggilan tersebut. Sementara itu, dalam kode berikut:

print(1,
      2,
      get_nesting_level())

panggilan untuk get_nesting_levelbersarang level nol dalam, meskipun ada spasi putih terkemuka di jalurnya.

Dalam kode berikut:

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

if True:
    print(get_nesting_level())

dua panggilan untuk get_nesting_levelberada pada tingkat bersarang yang berbeda, meskipun fakta bahwa spasi putih terkemuka identik.

Dalam kode berikut:

if True: print(get_nesting_level())

apakah itu level nol bersarang, atau satu? Dalam hal INDENTdan DEDENTtoken dalam tata bahasa formal, levelnya nol dalam, tetapi Anda mungkin tidak merasakan hal yang sama.


Jika Anda ingin melakukan ini, Anda harus memberi token seluruh file hingga titik panggilan dan menghitung INDENTserta DEDENTtoken. The tokenizeModul akan sangat berguna untuk fungsi seperti:

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
user2357112 mendukung Monica
sumber
2
Ini tidak berfungsi seperti yang saya harapkan ketika get_nesting_level()dipanggil dalam pemanggilan fungsi ― ia mengembalikan level bersarang dalam fungsi itu. Mungkinkah ditulis ulang untuk mengembalikan tingkat bersarang 'global'?
Fab von Bellingshausen
11
@ FabvonBellingshausen: Anda mungkin mendapatkan tingkat lekukan lekukan dan level fungsi panggilan bersarang. Fungsi ini memberikan tingkat lekukan lekukan. Level fungsi panggilan panggilan akan agak berbeda, dan akan memberikan tingkat 0 untuk semua contoh saya. Jika Anda menginginkan semacam lekukan / panggil nesting level hybrid yang meningkatkan untuk fungsi panggilan dan struktur aliran suka whiledan with, itu bisa dilakukan, tapi bukan itu yang Anda minta, dan mengubah pertanyaan untuk menanyakan sesuatu yang berbeda pada titik ini akan menjadi ide yang buruk.
user2357112 mendukung Monica
38
Kebetulan, saya setuju sepenuhnya dengan semua orang yang mengatakan ini adalah hal yang sangat aneh untuk dilakukan. Mungkin ada cara yang jauh lebih baik untuk menyelesaikan masalah apa pun yang Anda coba selesaikan, dan mengandalkan ini mungkin akan melumpuhkan Anda dengan memaksa Anda untuk menggunakan semua jenis peretasan jahat untuk menghindari perubahan indentasi atau struktur fungsi panggilan ketika Anda perlu membuat perubahan pada kode Anda.
user2357112 mendukung Monica
4
Saya tentu tidak berharap seseorang benar-benar menjawab ini. (pertimbangkan linecachemodul untuk hal-hal seperti ini - ini digunakan untuk mencetak tracebacks, dan dapat menangani modul yang diimpor dari file zip dan trik impor aneh lainnya)
Eevee
6
@ Eevee: Saya tentu tidak mengharapkan begitu banyak orang untuk memperbaiki ini! linecachemungkin baik untuk mengurangi jumlah file I / O (dan terima kasih telah mengingatkan saya tentang hal itu), tetapi jika saya mulai mengoptimalkannya, saya akan terganggu dengan bagaimana kita secara berlebihan melakukan re-tokenizing file yang sama untuk pengulangan file. panggilan yang sama, atau untuk beberapa situs panggilan dalam file yang sama. Ada beberapa cara untuk mengoptimalkannya juga, tetapi saya tidak yakin seberapa besar saya benar-benar ingin menyetel dan tahan terhadap hal gila ini.
user2357112 mendukung Monica
22

Ya, itu pasti mungkin, berikut ini contoh kerjanya:

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()
BPL
sumber
4
Berkaitan dengan komentar yang dibuat oleh @Prune, dapatkah ini dibuat untuk mengembalikan lekukan di tingkat alih-alih spasi? Apakah selalu baik-baik saja untuk membagi dengan 4?
Fab von Bellingshausen
2
Tidak, bagi dengan 4 untuk mendapatkan tingkat indentasi tidak akan berfungsi dengan kode ini. Dapat memverifikasi dengan meningkatkan tingkat indentasi pernyataan cetak terakhir, nilai cetak terakhir hanya meningkat.
Craig Burgler
10
Awal yang baik, tetapi tidak benar-benar menjawab pertanyaan imo. Jumlah spasi tidak sama dengan level indentasi.
wim
1
Tidak sesederhana itu. Mengganti 4 spasi dengan spasi tunggal dapat mengubah logika kode.
wim
1
Tetapi kode ini sempurna untuk apa yang dicari OP: (komentar OP # 9): "Saya ingin membuat indentasi kode saya sesuai dengan bagaimana indentasi dalam kode." Jadi dia bisa melakukan sesuatu sepertiprint('{Space}'*get_indentation_level(), x)
Craig Burgler
10

Anda dapat menggunakan sys.current_frame.f_linenountuk mendapatkan nomor baris. Kemudian untuk menemukan jumlah level indentasi, Anda perlu menemukan baris sebelumnya dengan indentasi nol lalu mengurangkan nomor baris saat ini dari nomor baris itu Anda akan mendapatkan jumlah indentasi:

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

Demo:

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

Jika Anda ingin jumlah level indentasi berdasarkan garis previouse dengan :Anda bisa melakukannya dengan sedikit perubahan:

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

Demo:

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

Dan sebagai jawaban alternatif di sini adalah fungsi untuk mendapatkan jumlah indentasi (spasi putih):

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]))
Kasramvd
sumber
pertanyaan yang diajukan untuk jumlah tingkat indentasi, bukan jumlah spasi. mereka tidak selalu proporsional.
wim
Untuk kode demo Anda, hasilnya harus 1 - 2 - 3 - 3
Craig Burgler
@CraigBurgler Untuk mendapatkan 1 - 2 - 3 - 3 kita dapat menghitung jumlah baris sebelum baris saat ini yang berakhir dengan a :sampai kita menemukan garis dengan lekukan nol, Periksa hasil edit!
Kasramvd
2
hmmm ... ok ... sekarang coba beberapa test case @ user2357112;)
Craig Burgler
@CraigBurgler Solusi ini hanya untuk sebagian besar kasus, dan mengenai jawaban itu, juga merupakan cara umum, itu tidak memberikan solusi yang komprehensif juga. Coba{3:4, \n 2:get_ind_num()}
Kasramvd
7

Untuk memecahkan masalah "nyata" yang mengarah ke pertanyaan Anda, Anda bisa menerapkan manajer konteks yang melacak tingkat indensi dan membuat withstruktur blok dalam kode sesuai dengan tingkat indentasi output. Dengan cara ini lekukan kode masih mencerminkan lekukan output tanpa terlalu banyak menyambung. Dimungkinkan untuk memperbaiki kode menjadi fungsi yang berbeda dan memiliki lekukan lain berdasarkan pada struktur kode yang tidak mengacaukan lekukan keluaran.

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

Keluaran:

0
  1
    Hallo 2
      3
    and back one level 2
Selikuran
sumber
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.
Craig Burgler
sumber
12
Ini memberikan lekukan di ruang, bukan di level. Kecuali jika programmer menggunakan jumlah indentasi yang konsisten, ini mungkin jelek untuk dikonversi ke level.
Prune
4
Apakah fungsinya tidak terdokumentasi? Saya tidak dapat menemukannya di sini
GingerPlusPlus