If you bind an event to the root window, that binding is attached to every widget. Thus, if you have a root window with four other widgets, when you destroy the window your bound function will be called five times - once for every widget.
An easy way to see this happen is to modify your function to not just print "OK", but to also print the widget associated with the event:
self.master.bind("<Destroy>", lambda event: print("{}: OK".format(event.widget)))
This has to do with the fact that you aren't actually binding to a widget per se, you are binding to a binding tag that has the same name as the widget. Every widget has an ordered set of binding tags associated with it in addition to itself, in the following order:
- It will have a binding tag for itself
- It will have a binding tag for the internal widget class (which is how widgets get their default behavior)
- It will have a binding tag for the toplevel window (or root) for that widget,
- It will have the special binding tag "all"
Thus, when you bind to the root window you are binding to a tag that is shared by every widget. If you want to bind to the root window and only the root window, the most common solution is to bind to a function, and in that function only take an action if the widget is the root window.
For example:
def on_destroy(self, event):
if event.widget == event.widget.winfo_toplevel():
print("{}: OK".format(event.widget))
self.master.bind("<Destroy>", self.on_destroy)