Home > Blockchain >  Unable to call sub-function when it is used with self but works fine without self
Unable to call sub-function when it is used with self but works fine without self

Time:10-17

I have a function, start_round() that initializes the round and creates temporary round variables. Then I have a nested function, rd_animate() that does the animation. Now when I make this rd_animate() function a method, the code does not work. When I do not include self, it works fine. Can anyone explain why this happens?

Code

Without self

def start_round(self):
    def rd_animate():
        #--::Animation::--#
        self.PF_ani_lbl.place(relx = 0.5, rely = 0.5, anchor = "center")
        self.PF_ani_lbl["text"] = "ROUND {}".format(self.round_no)
        self.after(500)
        self.PF_ani_lbl.place_forget()

    print("start_round")
    #--::Creation of round dictionary::--#
    self.round = {}

    #--::Animation::--#
    threading.Timer(0.05, rd_animate).start()

With self

def start_round(self):
    def rd_animate(self):
        #--::Animation::--#
        self.PF_ani_lbl.place(relx = 0.5, rely = 0.5, anchor = "center")
        self.PF_ani_lbl["text"] = "ROUND {}".format(self.round_no)
        self.after(500)
        self.PF_ani_lbl.place_forget()

    print("start_round")
    #--::Creation of round dictionary::--#
    self.round = {}

    #--::Animation::--#
    threading.Timer(0.05, self.rd_animate).start()

Error

It gives an error:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\raghavendra\anaconda3\lib\tkinter\__init__.py", line 1892, in __call__
    return self.func(*args)
  File "C:\Users\raghavendra\Desktop\Pratham\My Coding\MY APPS\RPS Multiplayer Game\root-recreated.py", line 348, in <lambda>
    command = lambda: self.start_match(retrieve()))
  File "C:\Users\raghavendra\Desktop\Pratham\My Coding\MY APPS\RPS Multiplayer Game\root-recreated.py", line 613, in start_match
    super().start_match(username, "Computer", match_settings)
  File "C:\Users\raghavendra\Desktop\Pratham\My Coding\MY APPS\RPS Multiplayer Game\root-recreated.py", line 533, in start_match
    self.start_round()
  File "C:\Users\raghavendra\Desktop\Pratham\My Coding\MY APPS\RPS Multiplayer Game\root-recreated.py", line 616, in start_round
    super().start_round()
  File "C:\Users\raghavendra\Desktop\Pratham\My Coding\MY APPS\RPS Multiplayer Game\root-recreated.py", line 445, in start_round
    self.rd_animate()
  File "C:\Users\raghavendra\anaconda3\lib\tkinter\__init__.py", line 2354, in __getattr__
    return getattr(self.tk, attr)
AttributeError: '_tkinter.tkapp' object has no attribute 'rd_animate'

I want to have it with self since it would be nice

CodePudding user response:

All tkinter parts of the code need to run in the same thread so running rd_animate in a separate thread is probably causing the problem. Replace the threading code with self.after. after returns immediately so .place_remove runs before the label is placed. Put the remove in a separate function and use that as a callback in .after

def start_round(self):

    def remove_label():
        self.PF_ani_lbl.place_forget()
        
    def rd_animate():
        #--::Animation::--#
        self.PF_ani_lbl.place(relx = 0.5, rely = 0.5, anchor = "center")
        self.PF_ani_lbl["text"] = "ROUND {}".format(self.round_no)
        self.after(500, remove_label )

    print("start_round")
    #--::Creation of round dictionary::--#
    self.round = {}

    #--::Animation::--#
    self.after( 50, rd_animate)

CodePudding user response:

Functions are just objects. As the error says, self is an instance of the tkinter app. It won't have your rd_animate function unless you set it.

self.rd_animate = rd_animate
threading.Timer(0.05, self.rd_animate).start()

You'll also want to create a partial function to actually pass the app instance into the parameter of the animate function. That way, the lines within the function won't throw an error saying 1 parameter is expected, but got zero


Alternatively, not really clear why you're nesting things since the above code would be similar to this since self refers to the enclosing class, only for functions definitely directly in the class body

class App(tk.Frame):
  def __init__(self, master):
    super().__init__(master) 
    self.round_no = 0
    self.PF_ani_lbl =... 

  def rd_animate(self):
    #--::Animation::--#
    self.PF_ani_lbl.place(relx = 0.5, rely = 0.5, anchor = "center")
    self.PF_ani_lbl["text"] = "ROUND {}".format(self.round_no)
    self.after(500)
    self.PF_ani_lbl.place_forget()

  def start_round(self):
    threading.Timer(0.05, self.rd_animate).start()

You can make rd_animate "private" function by adding two underscores in front like def __rd_animate

  • Related