Bir Python kodu satırı girintili iç içe geçme düzeyini bilebilir mi?


149

Böyle bir şeyden:

print(get_indentation_level())

    print(get_indentation_level())

        print(get_indentation_level())

Böyle bir şey almak istiyorum:

1
2
3

Kod bu şekilde kendini okuyabilir mi?

Tüm istediğim kodun daha iç içe bölümlerinden daha iç içe olması için çıktı. Bu, kodun okunmasını kolaylaştırdığı gibi, çıktıyı da okumayı kolaylaştırır.

Tabii ki bunu manuel olarak uygulayabilirim, örneğin .format(), ama aklımda olan şey, girinti seviyesinin print(i*' ' + string)nerede olacağı özel bir yazdırma iişleviydi. Bu, terminalimde okunabilir çıktı almanın hızlı bir yolu olacaktır.

El ile biçimlendirmeyi özenle önleyen bunu yapmanın daha iyi bir yolu var mı?


70
Neden buna ihtiyacın olduğunu merak ediyorum.
Harrison

12
@Harrison Kodumun çıktısına kodda nasıl girintili olduğuna göre girintilemek istedim.
Fab von Bellingshausen

14
Asıl soru şu: Buna neden ihtiyacınız var? Girinti seviyesi statiktir; get_indentation_level()ifadeyi kodunuza koyduğunuzda kesin olarak bilirsiniz . Siz de print(3)doğrudan ya da doğrudan yapabilirsiniz. Daha ilgi çekici olabilecek şey, işlev çağrısı yığınındaki geçerli iç içe yerleştirme düzeyidir.
tobias_k

19
Kodunuzda hata ayıklama amacıyla mı kullanılıyor? Bu, ya yürütme akışını kaydetmenin süper dahice bir yolu ya da basit bir sorun için aşırı mühendislik gerektiren bir çözüm gibi görünüyor ve hangisi olduğundan emin değilim ... belki ikisi de!
Blackhawk

7
@FabvonBellingshausen: Bu, umduğunuzdan çok daha az okunabilir gibi görünüyor. Ben açıkça bir depthparametre etrafında geçirerek ve diğer işlevlere geçirdiğinizde gerektiği gibi uygun değeri ekleyerek daha iyi hizmet olabilir düşünüyorum . Kodunuzun yuvalanmasının, çıktınızdan çıkmasını istediğiniz girintiye net bir şekilde karşılık gelmesi olası değildir.
user2357112 Monica

Yanıtlar:


115

Boşluklar ve sekmeler yerine iç içe yerleştirme düzeyi açısından girinti istiyorsanız, işler zorlaşır. Örneğin, aşağıdaki kodda:

if True:
    print(
get_nesting_level())

çağrı get_nesting_levelhattında önde gelen boşluk olmamasına rağmen , çağrı aslında bir seviye derinliğe yerleştirilmiştir get_nesting_level. Bu arada, aşağıdaki kodda:

print(1,
      2,
      get_nesting_level())

get_nesting_levelhattında önde gelen boşluk olmasına rağmen , çağrı sıfır seviyelerin derinliklerine yerleştirilir.

Aşağıdaki kodda:

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

if True:
    print(get_nesting_level())

get_nesting_levelönde gelen boşluk aynı olmasına rağmen iki çağrı farklı yuvalama düzeyindedir.

Aşağıdaki kodda:

if True: print(get_nesting_level())

bu iç içe sıfır seviyeleri mi yoksa bir mi? Biçimsel dilbilgisi INDENTve DEDENTjetonları açısından sıfır derinliktedir, ancak aynı şekilde hissetmeyebilirsiniz.


Bunu yapmak istiyorsanız, tüm dosyayı arama ve sayma INDENTve DEDENTbelirteçler noktasına kadar jetonlamak zorunda kalacaksınız . tokenizeBu modül, bir işlev için çok yararlı olacaktır:

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
Bu, get_nesting_level()bu işlev çağrısında çağrıldığında beklediğim şekilde çalışmaz that bu işlev içindeki yuvalama düzeyini döndürür. 'Global' yuvalama seviyesine dönmek yeniden yazılabilir mi?
Fab von Bellingshausen

11
@FabvonBellingshausen: Girinti yuvalama düzeyi ve işlev çağrısı yuvalama düzeyi karışık oluyor olabilir. Bu işlev girinti yuvalama seviyesi verir. İşlev çağrısı yuvalama düzeyi oldukça farklı olurdu ve tüm örneklerim için 0 düzeyi verecekti. Hem işlev çağrıları hem de kontrol akışı yapıları için artışlar yapan whileve withbu yapılabilir olan bir tür girinti / çağrı iç içe yerleştirme düzeyi hibriti istiyorsanız, ancak istediğiniz şey değildir ve bu noktada farklı bir şey sormak için soruyu değiştirmek kötü bir fikir olurdu.
user2357112 Monica

38
Bu arada, bunun gerçekten garip bir şey olduğunu söyleyen tüm insanlarla tamamen aynı fikirdeyim. Muhtemelen çözmeye çalıştığınız problemi çözmenin çok daha iyi bir yolu vardır ve buna güvenmek, girmeniz gerektiğinde girintinizi veya işlev çağrısı yapınızı değiştirmekten kaçınmak için sizi her türlü kötü hack'i kullanmaya zorlayarak muhtemelen hamstring olacaktır. kodunuzdaki değişiklikler.
user2357112 Monica

4
Kesinlikle birileri aslında bu cevap beklemiyorduk. ( linecachemodülü bunun gibi şeyler için düşünün - geri izleme yazdırmak için kullanılır ve zip dosyalarından ve diğer garip ithalat numaralarından içe aktarılan modülleri işleyebilir)
Eevee

6
@Eevee: Kesinlikle bu kadar çok insanın bunu oylamasını beklemiyordum ! linecachedosya G / Ç miktarını azaltmak için iyi olabilir (ve bana bunu hatırlattığınız için teşekkürler), ancak bunu optimize etmeye başlarsam, aynı dosyayı tekrarlamak için aynı dosyayı nasıl yeniden belirlediğimizden rahatsız olurum aynı çağrıda veya aynı dosyadaki birden fazla çağrı sitesi için. Bunu da optimize edebilmemizin birkaç yolu var, ancak bu çılgın şeyi ne kadar ayarlamak ve kurşun geçirmez hale getirmek istediğimi bilmiyorum.
user2357112,

22

Evet, bu kesinlikle mümkün, işte çalışan bir örnek:

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 tarafından yapılan yorumla ilgili olarak, girintiyi boşluklar yerine düzeylerde döndürmek için yapılabilir mi? Sadece 4'e bölmek her zaman uygun olur mu?
Fab von Bellingshausen

2
Hayır, girinti düzeyini almak için 4'e bölün bu kodla çalışmaz. Son baskı ifadesinin girinti düzeyini artırarak doğrulanabilir, son yazdırılan değer sadece artar.
Craig Burgler

10
İyi bir başlangıç, ama imo sorusuna gerçekten cevap vermiyor. Boşluk sayısı girinti seviyesiyle aynı değildir.
wim

1
O kadar basit değil. 4 boşluğun tek boşluklarla değiştirilmesi, kodun mantığını değiştirebilir.
wim

1
Ancak bu kod OP'nin aradığı şey için mükemmel: (OP yorum # 9): 'Kodumun kodunda girintiye göre kodumun çıktısını girintilemek istedim.' Böylece şöyle bir şey yapabilirprint('{Space}'*get_indentation_level(), x)
Craig Burgler

10

sys.current_frame.f_linenoSatır numarasını almak için kullanabilirsiniz . Ardından, girinti seviyesini bulmak için sıfır girintisi olan bir önceki satırı bulmanız ve ardından geçerli satır numarasını o satırın numarasından çıkarmanız gerekir, girintinin sayısını alırsınız:

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

Ön çim çizgilerine dayanan girinti seviyesinin sayısını :istiyorsanız, sadece küçük bir değişiklikle yapabilirsiniz:

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

Ve alternatif bir cevap olarak, girinti sayısını (beyaz boşluk) elde etmek için bir işlev var:

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

soru boşluk sayısını değil, girinti seviyelerinin sayısını istedi. mutlaka orantılı değildir.
wim

Demo kodunuz için çıktı 1 - 2 - 3 - 3
Craig Burgler

@CraigBurgler 1 - 2 - 3 - 3 elde etmek için :, sıfır girintili bir çizgi ile karşılaşana kadar bir ile biten mevcut satırdan önceki satır sayısını sayabiliriz , Düzenlemeye göz atın!
Kasramvd

2
hmmm ... tamam ... şimdi @ user2357112'nin test senaryolarından bazılarını deneyin;)
Craig Burgler

@CraigBurgler Bu çözüm çoğu durum için geçerlidir ve bu cevaba ilişkin olarak, aynı zamanda genel bir yoldur, kapsamlı bir çözüm de sunmaz. Deneyin{3:4, \n 2:get_ind_num()}
Kasramvd

7

Sorunuza yol açan "gerçek" sorunu çözmek için, girinti düzeyini takip eden ve withkoddaki blok yapısının çıktının girinti düzeylerine karşılık gelmesini sağlayan bir bağlam yöneticisi uygulayabilirsiniz. Bu şekilde kod girintisi, her ikisini de çok fazla birleştirmeden çıkış girintisini yansıtır. Kodu farklı işlevlere yeniden aktive etmek ve çıkış yapısıyla karışmayan kod yapısına dayalı başka girintilere sahip olmak hala mümkündür.

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

Çıktı:

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
Bu, boşluklarda girinti verir, seviyelerde değil. Programcı tutarlı girinti miktarları kullanmadığı sürece, bu seviyelere dönüştürmek çirkin olabilir.
Erik

4
İşlev belgelenmemiş mi? Burada
GingerPlusPlus
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.