Нумерация строк в Tkinter

Есть уже вот такой рабочий код:

import tkinter as tk


class TextLineNumbers(tk.Canvas):
    def __init__(self, *args, **kwargs):
        tk.Canvas.__init__(self, *args, **kwargs)
        self.textwidget = None

    def attach(self, text_widget):
        self.textwidget = text_widget

    def redraw(self, *args):
        '''redraw line numbers'''
        self.delete("all")

        i = self.textwidget.index("@0,0")
        while True :
            dline= self.textwidget.dlineinfo(i)
            if dline is None: break
            y = dline[1]
            linenum = str(i).split(".")[0]
            self.create_text(2,y,anchor="nw", text=linenum)
            i = self.textwidget.index("%s+1line" % i)


class CustomText(tk.Text):
    def __init__(self, *args, **kwargs):
        tk.Text.__init__(self, *args, **kwargs)

        # create a proxy for the underlying widget
        self._orig = self._w + "_orig"
        self.tk.call("rename", self._w, self._orig)
        self.tk.createcommand(self._w, self._proxy)

    def _proxy(self, *args):
        # let the actual widget perform the requested action
        cmd = (self._orig,) + args
        result = self.tk.call(cmd)

        # generate an event if something was added or deleted,
        # or the cursor position changed
        if (args[0] in ("insert", "replace", "delete") or 
            args[0:3] == ("mark", "set", "insert") or
            args[0:2] == ("xview", "moveto") or
            args[0:2] == ("xview", "scroll") or
            args[0:2] == ("yview", "moveto") or
            args[0:2] == ("yview", "scroll")
        ):
            self.event_generate("<<Change>>", when="tail")

        # return what the actual widget returned
        return result 


class Example(tk.Frame):
    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)
        self.text = CustomText(self)
        self.vsb = tk.Scrollbar(orient="vertical", command=self.text.yview)
        self.text.configure(yscrollcommand=self.vsb.set)
        self.text.tag_configure("bigfont", font=("Helvetica", "24", "bold"))
        self.linenumbers = TextLineNumbers(self, width=30)
        self.linenumbers.attach(self.text)

        self.vsb.pack(side="right", fill="y")
        self.linenumbers.pack(side="left", fill="y")
        self.text.pack(side="right", fill="both", expand=True)

        self.text.bind("<<Change>>", self._on_change)
        self.text.bind("<Configure>", self._on_change)

        self.text.insert("end", "one\ntwo\nthree\n")
        self.text.insert("end", "four\n",("bigfont",))
        self.text.insert("end", "five\n")

    def _on_change(self, event):
        self.linenumbers.redraw()


if __name__ == "__main__":
    root = tk.Tk()
    Example(root).pack(side="top", fill="both", expand=True)
    root.mainloop()

Но хотел превратить его на более простой код (без self и прочего нечитабельного кода) Вот так начал:

import tkinter as tk

root = tk.Tk()
tk.Frame(root).pack(side="top", fill="both", expand=True)

def _on_change(event):
    linenumbers.redraw()

def _proxy(*args):
    cmd = (_orig,) + args
    result = tk.call(cmd)

    if (args[0] in ("insert", "replace", "delete") or 
        args[0:3] == ("mark", "set", "insert") or
        args[0:2] == ("xview", "moveto") or
        args[0:2] == ("xview", "scroll") or
        args[0:2] == ("yview", "moveto") or
        args[0:2] == ("yview", "scroll")
    ):
        event_generate("<<Change>>", when="tail")
    return result 

text = tk.Text()
_orig = _w + "_orig"
tk.call("rename", _w, _orig)
tk.createcommand(_w, _proxy)

vsb = tk.Scrollbar(orient="vertical", command=text.yview)
text.configure(yscrollcommand=vsb.set)
text.tag_configure("bigfont", font=("Helvetica", "24", "bold"))

def attach(text_widget):
    textwidget = text_widget

def redraw(*args):
    '''redraw line numbers'''
    delete("all")

    i = textwidget.index("@0,0")
    while True :
        dline= textwidget.dlineinfo(i)
        if dline is None: break
        y = dline[1]
        linenum = str(i).split(".")[0]
        create_text(2,y,anchor="nw", text=linenum)
        i = textwidget.index("%s+1line" % i)

linenumbers = tk.Canvas(width=30)
linenumbers.attach(text)
textwidget = None

vsb.pack(side="right", fill="y")
linenumbers.pack(side="left", fill="y")
text.pack(side="right", fill="both", expand=True)

text.bind("<<Change>>", _on_change)
text.bind("<Configure>", _on_change)

text.insert("end", "one\ntwo\nthree\n")
text.insert("end", "four\n",("bigfont",))
text.insert("end", "five\n")

root.mainloop()

Ошибка в строчке _orig = _w + "_orig", следующая: NameError: name '_w' is not defined


Ответы (1 шт):

Автор решения: insolor

_w - это поле объекта Text, от которого в оригинале наследуется класс CustomText, поэтому в вашем коде нужно заменить _w на text._w:

text = tk.Text()
_orig = text._w + "_orig"
tk.call("rename", text._w, _orig)
tk.createcommand(text._w, _proxy)

Вообще, вытащить код из классов - так себе идея. Это не улучшение читаемости, а просто непонимание ООП. Был довольно хорошо структурированный код, стала сплошная портянка. Представьте, что вы в каком-то приборе сняли корпус, убрали все внутренние перегородки, в итоге куда ни коснись - или короткое замыкание, или где-то какую-то трубку передавит, или еще что-то. Не делайте так, лучше разберитесь с ООП.

→ Ссылка