0

This is a follow up question from this one where it is suggested to implement the bgerror to actually get a response to what went wrong in the background. However I'm a bit confused, as I understand the documentation it should be sufficient to define a proc named bgerror that will be called by the interpreter, doesn't it ?

My attempt below shows how I try to implement it, with no effect at all.

import tkinter as tk

tcl = tk.Tk()
####work around for puts in python
def puts(inp):
    print(inp)

cmd = tcl.register(puts)
tcl.eval(
    'proc puts {args} {' +
        f'{cmd} [join $args " "]' +
    '}')
tcl.call('puts', 'test') #test puts

#attempt to implement bgerror 
tcl.eval('''
proc bgerror {message} {
    set timestamp [clock format [clock seconds]]
    puts "$timestamp: bgerror in $::argv '$message'"
}''')

#setup a failing proc
tcl.eval('''
after 500 {error "this is an error"}
''')
tcl.mainloop()

In addition, it would be nice to know if this is the right approach at all, since the documentation suggests for newer applications to use interp bgerror. But again I'm confused, cause there seems to be no indicator where I could find the path for these calls or are they for child interpreter exclusively ?

2 Answers 2

3

In this case, you'd do better with explicit registration instead of using implicit registration based on naming, as that would let you access the full error information (including the stack trace!) and not just the message.

import tkinter
tcl = tkinter.Tcl()

# Fortunately, we can put the complex stuff in a function
def install_bgerror_handler(tcl, handler):
    def type_thunk(message, options):
        # Call internal command to convert Tcl dict to Python dict
        handler(message, tkinter._splitdict(tcl, options))
    tcl.createcommand(handler.__name__, type_thunk)
    tcl.call("interp", "bgerror", "", handler.__name__)

def bgerr_handler(message, options):
    print(message, "<<", options)

install_bgerror_handler(tcl, bgerr_handler)

# demonstrate with a background failure
tcl.eval('''
    after 500 {error "this is an error"}
''')
tcl.mainloop()

The message printed out in this case is: this is an error << {'code': '1', 'level': '0', 'errorstack': 'INNER {returnImm {this is an error} {}}', 'errorcode': 'NONE', 'errorinfo': 'this is an error\n while executing\n"error "this is an error""\n ("after" script)', 'errorline': '1'} The stack trace is available in the handler as options["errorinfo"]. The errorcode and errorline may also be interesting. (Conversion to being kwargs-based is left as an exercise, but be aware that the set of keys in that dictionary is not a closed set; more complex errors may include keys not listed above.)

It's a good idea to take special care to avoid actually throwing exceptions from your handler. Things will work, but it's probably a bad plan.

3
  • I've assumed that using __name__ is fine for generating a name. Proper production code would likely need some sort of symbol generation such as what tkinter does internally for callbacks. Commented May 1, 2023 at 11:18
  • I'm not sure why generating an unique name would be needed. Is that just because of caution, to not accidental overwriting the handler, or is there another reason for it? Commented May 1, 2023 at 11:30
  • Purely because of caution. Commented May 2, 2023 at 11:24
0

After a little bit of research, I've found out that this is an (long ago) unresolved issue in python. Compare "Issue 639266". To work around it, you have to use createcommand as it shows in the recent (2010) updated conversation.

Updated code looks like:

import tkinter as tk

tcl = tk.Tk()

#attempt to implement bgerror
def bgerr_handler(msg):
    print (msg, "<<")
tcl.tk.createcommand("bgerror", bgerr_handler)

#setup a failing proc
tcl.eval('''
after 500 {error "this is an error"}
''')
tcl.mainloop()

Update: They even closed this issue on github with:

This is now 20 years old and there was no agreement on the problem. If there is a problem to fix, it will be raised again.

Also note that the documentation itself states that:

If you are writing code that will be used by others as part of a package or other kind of library, consider avoiding bgerror. The reason for this is that the application programmer may also want to define a bgerror, or use other code that does and thus will have trouble integrating your code.

However, in the case of tkinter I would make an exception to this recommendation, since it is rather rare that python developer want to implement their own tcl bgerror and it's more likely they get surprised with a silent ignored exception and experience a "just not working code".

1
  • Defining a background error handler is fine and recommended for an application, but not so great for a library. Commented May 1, 2023 at 11:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.