Command config, quitting, binary, Timer

B

bearophileHUGS

Hello, I have four things to ask or to suggest, sorry if they seem
basic or already discussed.

-------------------

I am still ignorant about Tkinter. This little program, after pressing
the "Go" eats more and more RAM, is it normal? Can it be avoided? (In
normal programs this is isn't a real problem).

! import Tkinter
! def dogo():
! while 1:
! b.config(command=lambda:None)
! root = Tkinter.Tk()
! b = Tkinter.Button(root, text="Go", command=dogo)
! b.pack()
! root.mainloop()

Note: I have found this problem because in a small program I have a
Start button that becomes a Stop, so the bound command must change each
time. (Maybe there is some other solution, like hiding/unhiding the
buttons, I don't know.)

-------------------

When I quit a Tkinter program (clicking the [X] button of the window)
while it's running, it stops with something like this:

TclError: invalid command name ".9970192"

There is a way to avoid it, or to intercept that quitting command to
stop the program more gracefully?

-------------------

When I have to convert numbers to binary I use something like this (the
management of negative numbers is removed). It's my faster Python
version (derived by a Hettinger's version):

! from collections import deque
! def binary_conv(n):
! if n == 0:
! return 0
! else:
! absn = abs(n)
! conv = ["0", "1"]
! result = deque()
! _app = result.appendleft
! while absn:
! _app( conv[absn & 1] )
! absn >>= 1
! return int("".join(result))

But converting to binary is a quite common operation, so I think it can
be added as a binary function to Python, for example adding "b" to the
the conversion types of the % formatting.

-------------------

Sometimes I need something like the Timer of Java, it generates "ticks"
and calls a function for each tick. The tick frequency can be set, or
they can be stopped.

In another Newsgroup someone has suggested me that the callLater of
Twisted can solve my problem, but I think it's a quite common thing, so
I think that maybe it can be added to the threading standard module.

This is a rough Python version of mine (without comments), it's not a
true Metronome because it counts the delay time after the end of the
last function call. This class also seems fragile, sometimes it gives
me problems, and I cannot use too much concurrent metronomes, etc. It's
quite

Maybe someone can suggest me how to improve it.


! from threading import Timer
!
! class Metronome(object):
! def __init__(self, interval, fun, *args, **kwargs):
! self.interval = interval # seconds.
! self.fun = fun
! self.args = args
! self.kwargs = kwargs
! self._running = False
! def _go(self):
! # Call the function with the stored values
! self.fun(*self.args, **self.kwargs)
! if self._running:
! self._call = Timer(self.interval, self._go)
! self._call.start()
! def start(self):
! if not self._running:
! self._running = True
! self._go()
! def stop(self):
! if self._running:
! self._call.cancel()
! self._running = False

Thank you,
bearophile
 
P

Peter Hansen

I am still ignorant about Tkinter. This little program, after pressing
the "Go" eats more and more RAM, is it normal? Can it be avoided? (In
normal programs this is isn't a real problem).

! import Tkinter
! def dogo():
! while 1:
! b.config(command=lambda:None)
! root = Tkinter.Tk()
! b = Tkinter.Button(root, text="Go", command=dogo)
! b.pack()
! root.mainloop()

What did you expect to happen with the infinite loop inside dogo()?
Wouldn't it call b.config() over and over again, never returning? Is
that really what you wanted?

-Peter
 
D

Dennis Lee Bieber

! import Tkinter
! def dogo():
! while 1:
! b.config(command=lambda:None)

Well, this infinite loop can't be good for memory usage (I'm no
expert -- what does .config() do? [If I interpret the skimpy help
system, basically this one is replacing the original "dogo" method with
an anonymous function that "returns" None -- being in an endless loop,
you are creating an endless number of anonymous functions; they may not
be garbage collected until the loop ends]
Note: I have found this problem because in a small program I have a
Start button that becomes a Stop, so the bound command must change each
time. (Maybe there is some other solution, like hiding/unhiding the
buttons, I don't know.)
Would it not be easier to have a single callback, but change the
button Label, and have the callback branch based on the current label?
-------------------

When I quit a Tkinter program (clicking the [X] button of the window)
while it's running, it stops with something like this:

TclError: invalid command name ".9970192"

There is a way to avoid it, or to intercept that quitting command to
stop the program more gracefully?
No idea -- my only Tkinter program (at work, so I can't test it),
doesn't do anything like this.
-------------------

When I have to convert numbers to binary I use something like this (the
management of negative numbers is removed). It's my faster Python
version (derived by a Hettinger's version):

! from collections import deque
! def binary_conv(n):
! if n == 0:
! return 0
! else:
! absn = abs(n)
! conv = ["0", "1"]
! result = deque()
! _app = result.appendleft
! while absn:
! _app( conv[absn & 1] )
! absn >>= 1
! return int("".join(result))

But converting to binary is a quite common operation, so I think it can
be added as a binary function to Python, for example adding "b" to the
the conversion types of the % formatting.

Ugh...

And exactly what are you trying to return? That last line implies
you are returning an integer... If I understand the logic, you are
taking, say an integer of 3 (in binary "00000011"b, assuming only a byte
in length), creating a string of 0/1 ("11"), and then converting this
string back to an integer with the value 11 (binary "10000011")...


NYBBLES = { "0" : "0000",
"1" : "0001",
"2" : "0010",
"3" : "0011",
"4" : "0100",
"5" : "0101",
"6" : "0110",
"7" : "0111",
"8" : "1000",
"9" : "1001",
"A" : "1010",
"B" : "1011",
"C" : "1100",
"D" : "1101",
"E" : "1110",
"F" : "1111" }

def conv2b(n):
tstr = "%X" % n
olst = []
for nyb in tstr:
olst.append(NYBBLES[nyb])
return "".join(olst)

print
print "3 is ", conv2b(3)
print "11 is ", conv2b(11)
print "3.141 is ", conv2b(3.141)
print "-3 is ",conv2b(-3)
print "40558 is ", conv2b(40558)

-=-=-=-=-=-=-
3 is 0011
11 is 1011
3.141 is 0011
E:\UserData\Dennis Lee Bieber\My Documents\Python Progs\Script1.py:20:
FutureWarning: %u/%o/%x/%X of negative int will return a signed string
in Python 2.4 and up
tstr = "%X" % n
-3 is 11111111111111111111111111111101
40558 is 1001111001101110

I've not tested it, but I somehow suspect it is faster than a mass
of push/pop and shifts...

-------------------

Sometimes I need something like the Timer of Java, it generates "ticks"
and calls a function for each tick. The tick frequency can be set, or
they can be stopped.

In another Newsgroup someone has suggested me that the callLater of
Twisted can solve my problem, but I think it's a quite common thing, so
I think that maybe it can be added to the threading standard module.

This is a rough Python version of mine (without comments), it's not a
true Metronome because it counts the delay time after the end of the
last function call. This class also seems fragile, sometimes it gives
me problems, and I cannot use too much concurrent metronomes, etc. It's
quite
Main problem I see with this is that each call to threading.Timer()
is creating a NEW thread just for one tick. Lots of overhead involved.
I'd create a single thread whose main run method is a continuous loop,
using time.sleep() to suspend between ticks. .Timer() is optimized for a
one-time callback.

Note that using _nextInterval allows me perform an adjustment for
the time taken by the callback function.

-=-=-=-=-=-=-
import time
import threading

class Metronome(object):
def __init__(self, interval, func, *args, **kwargs):
self._func = func
self._args = args
self._kwargs = kwargs
self._interval = interval
self._running = False
self._Timer = None
self._nextInterval = 0

def Start(self):
if not self._running:
print "Creating worker at %s" % time.time()
self._Timer = threading.Thread(None, self._worker, (), {})
self._nextInterval = time.time() + self._interval
self._running = True
self._Timer.start()

def Stop(self):
if self._running:
print "Stopping worker at %s" % time.time()
self._running = False
self._Timer.join()
print "thread joined"

def _worker(self):
while self._running:
time.sleep(self._nextInterval - time.time())
self._nextInterval += self._interval
self._func(*self._args, **self._kwargs)


def aTick():
print "A tick happened at: %s" % time.time()

if __name__ == "__main__":
m = Metronome(5.0, aTick)
m.Start()
time.sleep(30)
m.Stop()
time.sleep(2.5)
m.Start()
time.sleep(20)
m.Stop()


--
 
D

Diez B. Roggisch

! import Tkinter
! def dogo():
! while 1:
! b.config(command=lambda:None)
! root = Tkinter.Tk()
! b = Tkinter.Button(root, text="Go", command=dogo)
! b.pack()
! root.mainloop()


I guess tkinter has to keep a name-reference pair (some days a
discussion about this arose and /F said something like that). It
certainly is somewhat ugly - but there might be very good reasons to do so.
Note: I have found this problem because in a small program I have a
Start button that becomes a Stop, so the bound command must change each
time. (Maybe there is some other solution, like hiding/unhiding the
buttons, I don't know.)

Had that to do myself - my solution was to use the buttons text for
swich-casing. I guess you change the buttons text anyways (otherwise
nobody would know that its semantic changed), so do something like this
(untetsted)

def start_stop_callback():
if b.get_config("text") == "Start":
b.config(text="Stop")
....
else:
b.config(text="Start")
....

Sometimes I need something like the Timer of Java, it generates "ticks"
and calls a function for each tick. The tick frequency can be set, or
they can be stopped.

In another Newsgroup someone has suggested me that the callLater of
Twisted can solve my problem, but I think it's a quite common thing, so
I think that maybe it can be added to the threading standard module.

This is a rough Python version of mine (without comments), it's not a
true Metronome because it counts the delay time after the end of the
last function call. This class also seems fragile, sometimes it gives
me problems, and I cannot use too much concurrent metronomes, etc. It's
quite

Maybe someone can suggest me how to improve it.

Instead of several metronomes, use one thread and compute the gresatest
common divisor for all scheduled timings. Use that as sleep interval
(your "ticks"), and count ticks for each timer. You should introduce a
lower thrshold on tick-length, otherwise you can get into trouble.


Diez
 
B

bearophileHUGS

Witn your suggestions and with some tests and work I've solved most of
the problems, thank you all for the comments.

Peter Hansen:
What did you expect to happen with the infinite loop inside dogo()?<

I expected that the same memory used by the b.config(command=...) can
be used by the successive update of the command. I am ignorant about
Tkinter, but to me it seems that no new memory is really needed each
loop.

-------------------

Dennis Lee Bieber:
you are creating an endless number of anonymous functions; they may not be garbage collected until the loop ends<

I don't know, usually the garbage collector is quite efficient :)

Would it not be easier to have a single callback, but change the button Label, and have the callback branch based on the current label?<

Right, thank you. It's not a perfect solution, but often it's good
enough.

No idea -- my only Tkinter program (at work, so I can't test it), doesn't do anything like this.<

With more tests and some googling I have found the problem and the
solution. It seems that the problem was produced by commands like this:
self.canvas.delete(self.id)
Maybe they are called after the Tkinter stop, or window close, or
something like that.

To solve the problem I've added this to the main window:
self.root.protocol('WM_DELETE_WINDOW', self.quitAll)

And inside the self.quitAll method I fix something before calling
self.root.quit()

I somehow suspect it is faster than a mass of push/pop and shifts...<

Your version is very interesting, thank you. I'll use this modified
version:

! _nibbles = {"0":"0000", "1":"0001", "2":"0010", "3":"0011",
! "4":"0100", "5":"0101", "6":"0110", "7":"0111",
! "8":"1000", "9":"1001", "A":"1010", "B":"1011",
! "C":"1100", "D":"1101", "E":"1110", "F":"1111",
! "-":"-"}
!
! def to_base2(number):
! result = [_nibbles[nibble] for nibble in "%X"%number]
! return int("".join(result))

This is faster than my version (but if you are using Psyco for all the
tests, for small numbers my version is faster).
This version is expecially faster for very long numbers.
But the difference between the two versions is quite little still, a C
function like the base2 conversion of GMPY is much faster, so I think
that such common operation can be added to the % formatting sintax.

Main problem I see with this is that each call to threading.Timer() is creating a NEW thread just for one tick. Lots of overhead involved. I'd create a single thread whose main run method is a continuous loop, using time.sleep() to suspend between ticks. .Timer() is optimized for a one-time callback. Note that using _nextInterval allows me perform an adjustment for the time taken by the callback function.<

Oh, very nice, you are very gentle, thank you. I'll add few suggestion
by Roggisch, and I think it will be quite good. If I obtain something
good I'll even think about putting it in the cookbook (with your names
in it).

-------------------

Diez B. Roggisch:
It certainly is somewhat ugly - but there might be very good reasons to do so.<
Okay.


Instead of several metronomes, use one thread and compute the gresatest common divisor for all scheduled timings. Use that as sleep interval (your "ticks"), and count ticks for each timer. You should introduce a lower thrshold on tick-length, otherwise you can get into trouble.<

Okay, I'll follow your suggestions.

Thank you all and hugs to Dennis Lee Bieber,
bearophile
 
D

Dennis Lee Bieber

I don't know, usually the garbage collector is quite efficient :)
Yes, but only when ref-counts go to 0... it may be that this tight
loop never allowed stuff to go to 0 ref-counts. It definitely never
returned control, so besides eating memory that way, any events for the
GUI framework were also not being handled and had to be queued.
Your version is very interesting, thank you. I'll use this modified
version:

! _nibbles = {"0":"0000", "1":"0001", "2":"0010", "3":"0011",
! "4":"0100", "5":"0101", "6":"0110", "7":"0111",
! "8":"1000", "9":"1001", "A":"1010", "B":"1011",
! "C":"1100", "D":"1101", "E":"1110", "F":"1111",
! "-":"-"}
!
! def to_base2(number):
! result = [_nibbles[nibble] for nibble in "%X"%number]
! return int("".join(result))
I'm still concerned about your use of that int() call... What is the
real use case for this conversion?

You take in a number -- which, for all but a few languages, is
internally binary (the exceptions being: true REXX -- using character
strings for everything, COBOL -- traditionally using BCD arithmetic
unless one declared a variable usage as COMPutational, and maybe PL/1).
You then convert this to a human readable representation of binary
(using character 1 and 0 for each bit)... So far I understand it. But
then you feed these string representation of a binary number to a
function that treats it as a string representation of a DECIMAL number,
and converts said decimal back to an internal binary format which bears
no resemblence to the 0/1 bits you started with.

You can't do arithmetic with the result: Say 2 + 3 were the
originals...

2d => 0010b
3d => 0011b
+(in native binary)
5d => 0101b

but in your method, feeding back through int()

2d => 0010b => 10d (1010b)
3d => 0011b => 11d (1011b)
+ +
5d => 0101b !-> 21d (00010101b)

If the only purpose is to get a displayable binary value, leave the
result as a string and don't call int(). Actually, with leading 0s, that
int() call is probably interpreting the string as OCTAL, rather than
DECIMAL.
This is faster than my version (but if you are using Psyco for all the
tests, for small numbers my version is faster).

For moderate numbers, mine is using the, hopefully quite optimized
string formatting operations (which should be nearly as fast as doing
the same in C: sprintf(stringbuff, "%X", n);), and then doing hopefully
fast dictionary lookups to get the pieces.

--
 
B

bearophileHUGS

Dennis Lee Bieber:
Yes, but only when ref-counts go to 0... it may be that this tight loop never allowed stuff to go to 0 ref-counts. It definitely never returned control, so besides eating memory that way, any events for the GUI framework were also not being handled and had to be queued.<

This memory allocation problem is present even without the loop. In
this other example if you keep cliking on the Go button, the memory
used keep growing:

from Tkinter import *
def dogo(): b.config(command=dogo)
root = Tk()
b = Button(root, text="Go", command=dogo)
b.pack()
root.mainloop()

with leading 0s, that int() call is probably interpreting the string as OCTAL, rather than DECIMAL.<

This can be fixed with a different dictionary that doesn't contain the
leading 0s, to be used just for the first (not the "-") nibble.

What is the real use case for this conversion?<

You are right, I'll remove the int conversion.

Thank you, a bear hug,
Bearophile
 
B

bearophileHUGS

Bearophile>This can be fixed with a different dictionary that doesn't
contain the leading 0s,<

No other dict is necessary:

! _nibbles = {"0":"0000", "1":"0001", "2":"0010", "3":"0011",
! "4":"0100", "5":"0101", "6":"0110", "7":"0111",
! "8":"1000", "9":"1001", "A":"1010", "B":"1011",
! "C":"1100", "D":"1101", "E":"1110", "F":"1111",
! "-":"-"}
!
! def toBase2(number):
! """toBase2(number): given an int/long, converts it
! to a string containing the number in base 2."""
! # From a suggestion by Dennis Lee Bieber.
! if number == 0:
! return "0"
! result = [_nibbles[nibble] for nibble in "%X"%number]
! result[number<0] = result[number<0].lstrip("0")
! return "".join(result)
!
! for i in xrange(-20, 21):
! print toBase2(i)

Bye,
Bearophile
 
D

Dennis Lee Bieber

This memory allocation problem is present even without the loop. In
this other example if you keep cliking on the Go button, the memory
used keep growing:
No idea then -- I'd hope the .mainloop process wasn't that
 

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,995
Messages
2,570,230
Members
46,817
Latest member
DicWeils

Latest Threads

Top