TK program problem

B

bvdp

I've just done an update to my system here to Ubuntu 11.04. Mostly no
problems ... but I have an important (to me) python/TK program that's
stopped working. Well, it works ... mostly.

The python version is 2.7.1+ (no idea what the + means!).

I _think_ I have traced the problem to certain menus which call a
class. The calls appear to be ignored.

Basically, what I have is a line like:

bf = makeButtonBar(root, row=0, column=0, buttons=(
("Quit", self.quitall ),
("Stop", self.stopPmidi ),
("New Dir", self.chd),
("Load Playlist", self.playList),
("Favorites", selectFav),
("Options", setOptions) ) )

To create a menu bar. The function makeButtonBar() creates the buttons
with:

for txt, cmd in buttons:
Button(bf, text=txt, height=1, command=cmd).grid(column=c,
row=0, pady=5)


All this is fine (and worked perfectly before my upgrade). The menu
items which are ordinary functions continue to work. BUT the callbacks
which are classes are just ignored when they are clicked.

A cut from one of the ignored classes:


class selectFav:

def __init__(self):
...

And I've inserted some prints in the __init__() and nothing is
printed. Also, converted the class to new-style () but no change there
either.

Either python/tk has changed or my system is totally $*(#*#.
Suggestions welcome!
 
I

Ian Kelly

All this is fine (and worked perfectly before my upgrade). The menu
items which are ordinary functions continue to work. BUT the callbacks
which are classes are just ignored when they are clicked.

I'm not a tk user, but it sounds like it has regressed from accepting
arbitrary callables as callbacks to accepting functions specifically.

What happens if you replace:

("Favorites", selectFav),

with:

("Favorites", lambda: selectFav()),
 
B

bvdp

I'm not a tk user, but it sounds like it has regressed from accepting
arbitrary callables as callbacks to accepting functions specifically.

What happens if you replace:

("Favorites", selectFav),

with:

("Favorites", lambda: selectFav()),

Okay, this works. Great and thanks! Seems to me that the way I was
doing it should be alright ... and I've got some other programs
exhibiting the same problem.

Before I go "fixing" the issue ... is this known or even a bug?
 
I

Ian Kelly

Okay, this works. Great and thanks! Seems to me that the way I was
doing it should be alright ... and I've got some other programs
exhibiting the same problem.

Before I go "fixing" the issue ... is this known or even a bug?

The docs [1] say that a callback is a function, so I guess that if it
worked before it was just luck. You should bring it up on the tkinter
list and see what they have to say about it, though.

I'm a bit confused about why you would want to use a class as a
callback anyway. It looks like when the button is clicked it
instantiates the class and then throws it away?

[1] http://docs.python.org/library/tkinter.html#tk-option-data-types
 
B

bvdp

Probably the fix is to use a function :)
The docs [1] say that a callback is a function, so I guess that if it
worked before it was just luck.  You should bring it up on the tkinter
list and see what they have to say about it, though.

I'm a bit confused about why you would want to use a class as a
callback anyway.  It looks like when the button is clicked it
instantiates the class and then throws it away?

I have no idea why I used a class like this, expect that it seemed to
work at the time. Yes, the class is instantiated when the button is
clicked. Then, the user stays in the class and uses its methods until
he hits <close> in the class.

So, I guess what's happening is that I'm treating the button click
much like a call to a new program/window which sets some options, etc.
in the main program.

You mention the tkinter group. Ummm, what group is that???

Best,
 
R

rantingrick

Probably the fix is to use a function :)
The docs [1] say that a callback is a function, so I guess that if it
worked before it was just luck.  You should bring it up on the tkinter
list and see what they have to say about it, though.
I'm a bit confused about why you would want to use a class as a
callback anyway.  It looks like when the button is clicked it
instantiates the class and then throws it away?

I have no idea why I used a class like this, expect that it seemed to
work at the time. Yes, the class is instantiated when the button is
clicked. Then, the user stays in the class and uses its methods until
he hits <close> in the class.

Thats sounds to me a lot like hammering square pegs though round
holes... Perhaps you should explain first in "plain english" what
problem your application is intended to solve, then how it is expected
to interact with the user, and finally, what exactly is NOT working
correctly. I would suffix such a documentary with the current source
code, verbatim.
 
P

Peter Otten

bvdp said:
I've just done an update to my system here to Ubuntu 11.04. Mostly no
problems ... but I have an important (to me) python/TK program that's
stopped working. Well, it works ... mostly.

The python version is 2.7.1+ (no idea what the + means!).

I _think_ I have traced the problem to certain menus which call a
class. The calls appear to be ignored.

Basically, what I have is a line like:

bf = makeButtonBar(root, row=0, column=0, buttons=(
("Quit", self.quitall ),
("Stop", self.stopPmidi ),
("New Dir", self.chd),
("Load Playlist", self.playList),
("Favorites", selectFav),
("Options", setOptions) ) )

To create a menu bar. The function makeButtonBar() creates the buttons
with:

for txt, cmd in buttons:
Button(bf, text=txt, height=1, command=cmd).grid(column=c,
row=0, pady=5)


All this is fine (and worked perfectly before my upgrade). The menu
items which are ordinary functions continue to work. BUT the callbacks
which are classes are just ignored when they are clicked.

A cut from one of the ignored classes:


class selectFav:

def __init__(self):
...

And I've inserted some prints in the __init__() and nothing is
printed. Also, converted the class to new-style () but no change there
either.

Either python/tk has changed or my system is totally $*(#*#.
Suggestions welcome!

Here's a minimal script to reproduces the problem:

$ cat tkcallclass.py
import Tkinter as tk

root = tk.Tk()
root.withdraw()

class Classic:
def __init__(self):
print "hello"

button = tk.Button(root, command=Classic)
button.invoke()
$ python2.6 tkcallclass.py
hello
$ python2.7 tkcallclass.py
Traceback (most recent call last):
File "tkcallclass.py", line 11, in <module>
button.invoke()
File "/usr/local/lib/python2.7/lib-tk/Tkinter.py", line 2081, in invoke
return self.tk.call(self._w, 'invoke')
_tkinter.TclError: invalid command name "__main__.Classic"
$

In 2.7 the Tkinter code was changed to use hasattr(obj, "__call__") instead
of callable(obj) to recognize callbacks. This gives different results for
oldstyle classes
False

....and they are no longer registered automatically with Tkinter. In theory
you could register them explicitly yourself

$ cat tkcallclass2.py
import Tkinter as tk

root = tk.Tk()
root.withdraw()

class Classic:
def __init__(self):
print "hello"

button = tk.Button(root, command=root.register(Classic))
button.invoke()
$ python2.7 tkcallclass2.py
hello

but in practice changing them to newstyle (i. e. have them inherit from
object) or wrapping them in a lambda appears convenient.

Personally, I would reconsider whether using a class as a callback is really
necessary. Replacing the class with a function should be a straightforward
process.
 
B

bvdp

Thanks, Peter, for the detailed explanation. I was going to write a
bit of sample/minimal code to demo this, but you nicely beat me to
it!
Here's a minimal script to reproduces the problem:

$ cat tkcallclass.py
import Tkinter as tk

root = tk.Tk()
root.withdraw()

class Classic:
    def __init__(self):
        print "hello"

button = tk.Button(root, command=Classic)
button.invoke()
$ python2.6 tkcallclass.py
hello
$ python2.7 tkcallclass.py
Traceback (most recent call last):
  File "tkcallclass.py", line 11, in <module>
    button.invoke()
  File "/usr/local/lib/python2.7/lib-tk/Tkinter.py", line 2081, in invoke
    return self.tk.call(self._w, 'invoke')
_tkinter.TclError: invalid command name "__main__.Classic"
$

Any idea why I'm not getting any traceback in my program? It just runs
and appears to ignore the callback.
In 2.7 the Tkinter code was changed to use hasattr(obj, "__call__") instead
of callable(obj) to recognize callbacks. This gives different results for
oldstyle classes


False

...and they are no longer registered automatically with Tkinter. In theory
you could register them explicitly yourself

$ cat tkcallclass2.py
import Tkinter as tk

root = tk.Tk()
root.withdraw()

class Classic:
    def __init__(self):
        print "hello"

button = tk.Button(root, command=root.register(Classic))
button.invoke()
$ python2.7 tkcallclass2.py
hello

but in practice changing them to newstyle (i. e. have them inherit from
object) or wrapping them in a lambda appears convenient.

Yes, I can confirm that both the lambda and setting the class to:

class selectFav(object):

works.
Personally, I would reconsider whether using a class as a callback is really
necessary. Replacing the class with a function should be a straightforward
process.

IIRC, I used the class method since it nicely encapsulates a set of
operations:

- create/raise a window
- list a set of configurable options
- have <cancel> and <save> buttons, both of which destroy the
window.

Having a function as the callback certainly works as well. Not sure
which is the best method in the long run ... I'm trying to use classes
more in my programming since it's nice to wrap a bunch of functions up
like this.

Thanks again.
 
B

bvdp

Thats sounds to me a lot like hammering square pegs though round
holes... Perhaps you should explain first in "plain english" what

Ahh, but what fun would the Internet, Usenet and programming be
without round holes and square pegs.

I thought my English was pretty good.
problem your application is intended to solve, then how it is expected

I'm trying very much to focus on the problem I'm having with a
particular bit of python code. I'm not trying to have the community
solve my coding problems.
to interact with the user, and finally, what exactly is NOT working
correctly. I would suffix such a documentary with the current source
code, verbatim.

You want me to attach several hundred/thousand lines of source code to
demonstrate that a particular bit of python has changed behavior
between versions?
 
T

Terry Reedy

Yes, I can confirm that both the lambda and setting the class to:

class selectFav(object):

One of the purposes and advantages of Python 3 is having only one class
system. Best to always use new-style classes in Python 2.2+ unless you
understand and need old-style classes (and need should be never for most
people).
 
B

bvdp

One of the purposes and advantages of Python 3 is having only one class
system. Best to always use new-style classes in Python 2.2+ unless you
understand and need old-style classes (and need should be never for most
people).

Thanks for this. I'll keep it in mind!

One thing I really don't understand ... is there a difference between
the old/new forms:

class foo:
class foo():

In cases where I've played with them, they _appear_ to work the same?
Also, where does one find the magic that says that for a tkinter class
you should use:

class foo(object):

Not really sure where "object" comes from.

Thanks and best,
 
R

rantingrick

IIRC, I used the class method since it nicely encapsulates a set of
operations:

   - create/raise a window
   - list a set of configurable options
   - have <cancel> and <save> buttons, both of which destroy the
window.

Ok NOW we are getting somewhere! It is amazing how helpful people can
be when you explain your problem in "plain English". What you are
describing here is a dialog (be it modal or not). However your earlier
description of...

"""Yes, the class is instantiated when the button is clicked. Then,
the user stays in the class and uses its methods until he hits <close>
in the class. """

.... was about as effective as the "orb of confusion" on Patrick...
*drool*
Having a function as the callback certainly works as well. Not sure
which is the best method in the long run ... I'm trying to use classes
more in my programming since it's nice to wrap a bunch of functions up
like this.

Yes you ABSOLUTELY SHOULD encapsulate the dialog code in a class! THEN
create an instance of the class inside a "easy to call" function.

class MyDialog(blah):
blah,blah,blah

def show_dialog(*args):
d = MyDialog(*args)
return d.result

result = show_dialog()

....and they all lived happily ever after ;-)
 
C

Chris Angelico

... was about as effective as the "orb of confusion" on Patrick...
*drool*

That sounds like a Dungeons and Dragons artifact item... invoking the
orb of confusion is a standard action that does not provoke an Attack
of Opportunity.
class MyDialog(blah):
blah,blah,blah

def show_dialog(*args):
d = MyDialog(*args)
return d.result

result = show_dialog()

I don't really see why it shouldn't be valid to use the class itself
in this way. Once the constructor returns, the object IS the return
value.

But then, my opinion may not be valid. I've done weird things in C++
where, for instance, the invocation of a modeless dialog is:

new FoobarDialog(params);

And somewhere it has an event (eg when the window is destroyed) that executes:

delete this;

I'm a little mad and generally silly, so my opinion doesn't matter
(matter matter matter matter). But it did work, and the code was nice
and clean!

Chris Angelico
 
T

Terry Reedy

Thanks for this. I'll keep it in mind!

One thing I really don't understand ... is there a difference between
the old/new forms:

class foo:
class foo():

In cases where I've played with them, they _appear_ to work the same?

I believe they are. Same is true in 3.x except that the result in a
new-style class.
Also, where does one find the magic that says that for a tkinter class
you should use:

class foo(object):

Perhaps nowhere. It may have been an unintended side-effect of the
change in callable check, or intentional but not documented.
Not really sure where "object" comes from.

It is the base class of all (new-style) classes.<object object at 0x00EB6668>
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,982
Messages
2,570,190
Members
46,736
Latest member
zacharyharris

Latest Threads

Top