Non-blocking Python

A place to discuss the implementation and style of computer programs.

Moderators: phlip, Moderators General, Prelates

User avatar
Kerberos
Posts: 189
Joined: Sun Oct 21, 2007 1:41 am UTC
Location: Male

Non-blocking Python

Postby Kerberos » Sat Nov 01, 2008 10:27 pm UTC

I'm trying to design a rudimentary chat program in Python. However, this requires simultaneously listening for messages from the other end and looking for input from the keyboard. I don't know how to do either of those processes (socket.recv, raw_input) without having Python wait for it to finish. Does anyone else?

User avatar
Berengal
Superabacus Mystic of the First Rank
Posts: 2707
Joined: Thu May 24, 2007 5:51 am UTC
Location: Bergen, Norway
Contact:

Re: Non-blocking Python

Postby Berengal » Sat Nov 01, 2008 11:37 pm UTC

Use threads. Take a look at the threading module.

The basic way to do this is to have a listener thread that listens for incoming connections (blocking), and whenever it receives one it spawns off another thread which we'll call a connection, before resuming listening for more connections. A connection listens for input on it's already bound socket, and whenever it gets input it runs whatever parsing and function calling it has to before resuming listening for more input on it's specific socket connection. You'll probably want a central server object for storing all connections and for putting the various methods in. This object is shared across all threads of execution and is the common ground on which all the connections communicate.

The above describes the usual server program. It doesn't concern itself with user-input, only client-program-input. User input is the job of the client programs to deal with: The client first connects to the server and gets a bound socket. It then spawns off a thread that listens for input on this socket, the listener, and when input is received it prints it, or does whatever it wants with it really. Another thread, probably the main thread, listens for input from the keyboard, and sends that input through the same socket connection the listener is listening to.

The server is non-interactive. That is, you as a human don't interact with it apart from starting it up and shutting it down. To use it yourself, you start the client program as a separate process, or you could possibly have the server start one up for you in a separate thread if you want to with an already bound socket (or since it's python, you could just feed it any stream that allows for reading and writing, such as a combined stdin/stdout stream). The point is that everything you type on your keyboard is fed only to the client, and everything that is written is only written by the client; the server doesn't concern itself with anything but relaying stuff between it's connections.

Some pseudopython to confuse you even more. This is what a server might look like:

Code: Select all

#The server program!
class Server(object):

  def __init__(self):
    self.clients = []

  def connectClient(self, bound_socket):
    client = Client(bound_socket, self)
    self.clients.append(client)
    client.start() #this spawns off the new thread
    self.sendAll("%s has entered the chat" % client.nick)

  def disconnectClient(self, client):
    if not client in self.clients:
      raise Exception("Disconnection non-connected client")
    self.sendAll("%s has left the chat" % client.nick)
    self.clients.remove(client)

  def sendAll(self, message):
    for client in self.clients:
      client.send(message)

# Still part of the server. This is the client that the server program sees, not the client we're usually talking about

class Client(threading.Thread):
  def __init__(self, bound_socket, server):
    self.socket = bound_socket
    self.server = server
    self.nick = self.socket.read()

  def run(self):
    while self.socket.isConnected():
      input = self.socket.read()
      self.server.sendAll(%s: %s" % (self.nick, input))
    self.server.disconnectClient(self)
    self.socket.close()

#The listener could very well be the main thread:

if __name__ == "__main__":
  listener_socket = Socket()
  server = Server()
  while True:
    bound_socket = listener_socket.connect()
    server.connectClient(bound_socket)


This is a very basic server. I've left out all the details in setting up the sockets. When this server program is run you could connect to it with any program, such as telnet or even netcat. The next step is to write a client and to implement a protocol. You could force the clients to prepend e.g "SAY " to any message that is to be broadcasted to everyone, and your client program could do that automatically. This allows you to make new commands available to the clients, such as e.g. "PARTICIPANTS" which would cause the server to send "PARTICIPANT_LIST " followed by the names of everyone in the chat to only the client that requested the list, or the client could send "NICK " and a new nickname, or "WHISPER " to allow private messaging... I'm sure you can think of lots of stuff.

I think this is a great way to learn both basic threading and network programming. A good choice for learning indeed :D
It is practically impossible to teach good programming to students who are motivated by money: As potential programmers they are mentally mutilated beyond hope of regeneration.

User avatar
Kerberos
Posts: 189
Joined: Sun Oct 21, 2007 1:41 am UTC
Location: Male

Re: Non-blocking Python

Postby Kerberos » Sun Nov 02, 2008 12:03 am UTC

Heh. You, uh... could've just said, "The keyword you want is 'thread'." Thanks for going to the trouble you did, though; it's nice to see other people write nice, organized code, and I feel more at home in Socketland now.

Edit: ignore whatever you may have seen here before. I need to learn about threads.

User avatar
Berengal
Superabacus Mystic of the First Rank
Posts: 2707
Joined: Thu May 24, 2007 5:51 am UTC
Location: Bergen, Norway
Contact:

Re: Non-blocking Python

Postby Berengal » Sun Nov 02, 2008 12:15 am UTC

I have an assignment due in a week, which is why I take every opportunity I can get to do something completely unrelated. I'm also way past sleepy.
It is practically impossible to teach good programming to students who are motivated by money: As potential programmers they are mentally mutilated beyond hope of regeneration.

User avatar
sparkyb
Posts: 1091
Joined: Thu Sep 06, 2007 7:30 pm UTC
Location: Camberville proper!
Contact:

Re: Non-blocking Python

Postby sparkyb » Sun Nov 02, 2008 5:20 am UTC

With sockets, you can also make them non-blocking by using socket.settimeout. If you set it to None then it will be a blocking socket. If you set the timeout to a float it will be the number of seconds to block for if no input is received before raising a socket.timeout exception. You can set it to 0 to make a socket that will return immediately. Obviously you will want to put this in a try/except block where you except socket.timeout in the case there is no input to process.

I often use a timeout of like a second or so even with threading. Even though the socket listening is in a separate thread so that the user control is still responsive, there is no way to kill a thread in in Python and sometimes a blocked socket call can cause a program not to exit. This is why I'll use at least a 1 or 10 second timeout, so that if the program is terminated, after at most that timeout when the socket unblocks, the thread can end before it comes around the loop again.

For input, if you use windows you can use the msvcrt.kbhit() function to test if a key has been typed and msvcrt.getch() to get the key (blocking but that's why you use kbhit() to make sure that there is input so getch() won't block). There is probably an alternative that I don't know for other platforms. I don't know any platform independent calls in the python standard library for doing character-based (as opposed to line-based) input. It is probably not what you want to do, for several reasons, I just wanted to throw it out there.

User avatar
Kerberos
Posts: 189
Joined: Sun Oct 21, 2007 1:41 am UTC
Location: Male

Re: Non-blocking Python

Postby Kerberos » Sun Nov 02, 2008 9:01 pm UTC

I see. So could I have a potential memory leak problem if I don't make sure all of my threads die eventually? If so, would 'del MyThread' be an effective (if brutal) way of ending one?

User avatar
jemthealmighty
Posts: 145
Joined: Fri May 23, 2008 6:55 am UTC
Location: Dubbo, NSW, Australia
Contact:

Re: Non-blocking Python

Postby jemthealmighty » Sun Nov 02, 2008 9:23 pm UTC

Kerberos wrote:I see. So could I have a potential memory leak problem if I don't make sure all of my threads die eventually? If so, would 'del MyThread' be an effective (if brutal) way of ending one?

From my experiences (may not be correct) yes it can cause a memory leak but you can only terminate a thread from itself (ie. the parent thread can't terminate it) so using timeouts is a really good idea.

User avatar
Kerberos
Posts: 189
Joined: Sun Oct 21, 2007 1:41 am UTC
Location: Male

Re: Non-blocking Python

Postby Kerberos » Sun Nov 02, 2008 9:32 pm UTC

Okay, so if I say

MySocket.settimeout(7)

Then have MySocket do a 5-second operation, then another 5-second operation, then a 10-second operation, what happens?

User avatar
Berengal
Superabacus Mystic of the First Rank
Posts: 2707
Joined: Thu May 24, 2007 5:51 am UTC
Location: Bergen, Norway
Contact:

Re: Non-blocking Python

Postby Berengal » Sun Nov 02, 2008 9:41 pm UTC

It's not really a memory-leak problem as much as a thread-leak problem. You don't want threads lying around that you're not using. They might suddenly resume...

Just using del won't work. The problem can be fixed by making sure no thread ever blocks; that all threads check if they should continue running every few seconds at least, or by making sure your potential blocking threads aren't responsible for any resources and using exit() to quit the program. exit() kills off the entire process, which stops all threads right away whatever they were doing, so make sure you've cleaned up after yourself before you do this, and that the only threads running are the permanently blocking ones that you don't care about anymore. You should provide for the cases where a blocking thread suddenly resumes while you're cleaning up the rest of the system. Killing off the process is crude, but it works if you're having trouble with blocking io, which can be a bother in python.

I think python 3k will have portable support for non-blocking io, but only at the raw io layer, which means you'll have to go pretty low-level compared to what you're usually used to.
It is practically impossible to teach good programming to students who are motivated by money: As potential programmers they are mentally mutilated beyond hope of regeneration.

User avatar
Kerberos
Posts: 189
Joined: Sun Oct 21, 2007 1:41 am UTC
Location: Male

Re: Non-blocking Python

Postby Kerberos » Mon Nov 03, 2008 12:27 am UTC

Berengal wrote:Just using del won't work. The problem can be fixed by making sure no thread ever blocks...

Aw, man. Now I have to code in an organized, logical manner. Life sucks.

Thanks for the help, everyone, I think I get it now.

User avatar
Berengal
Superabacus Mystic of the First Rank
Posts: 2707
Joined: Thu May 24, 2007 5:51 am UTC
Location: Bergen, Norway
Contact:

Re: Non-blocking Python

Postby Berengal » Mon Nov 03, 2008 4:26 am UTC

Kerberos wrote:
Berengal wrote:Just using del won't work. The problem can be fixed by making sure no thread ever blocks...

Aw, man. Now I have to code in an organized, logical manner. Life sucks.

Threading can do that to you...
It is practically impossible to teach good programming to students who are motivated by money: As potential programmers they are mentally mutilated beyond hope of regeneration.

User avatar
jemthealmighty
Posts: 145
Joined: Fri May 23, 2008 6:55 am UTC
Location: Dubbo, NSW, Australia
Contact:

Re: Non-blocking Python

Postby jemthealmighty » Mon Nov 03, 2008 7:11 am UTC

Berengal wrote:Threading can do that to you...

Agreed :p

This is a module I found a while back and modified a bit to suit my liking... it may help you:

Code: Select all

import threading

class TimeoutFunctionException(Exception):
    """Exception for handling timeouts."""
    pass

class TimeoutFunction(threading.Thread):
    """Class for running functions under a time limit."""
    def __init__(self, function, timeout):
        self.timeout = timeout
        self.function = function
    def run(self, *args):
        try:
            self.result = self.function(*args)
        except:
            return
    def __call__(self, *args):
        threading.Thread.__init__(self)
        self.start() # calls self.run()
        self.join(self.timeout)
        if self.isAlive():
            self.join(0)
            raise TimeoutFunctionException()
        else:
            return self.result

To use it:

Code: Select all

time_out = 3 #seconds
def function_to_run(arg1,arg2,arg3):
    return True

timed_thread_object = TimeoutFunction(function_to_run,time_out)

result = timed_thread_object(1,"string",['l','i','s','t'])


*** EDIT ***

Berengal in your post above:
Berengal wrote:Some pseudopython to confuse you even more. This is what a server might look like:

Code: Select all

...
if __name__ == "__main__":
  listener_socket = Socket()
  server = Server()
  while True:
    bound_socket = listener_socket.connect()
    server.connectClient(bound_socket)


where is

Code: Select all

Socket()

defined?

User avatar
Berengal
Superabacus Mystic of the First Rank
Posts: 2707
Joined: Thu May 24, 2007 5:51 am UTC
Location: Bergen, Norway
Contact:

Re: Non-blocking Python

Postby Berengal » Mon Nov 03, 2008 10:39 am UTC

In the pseudosocket module, and it's imported by default by any conforming pseudopython implementation.
It is practically impossible to teach good programming to students who are motivated by money: As potential programmers they are mentally mutilated beyond hope of regeneration.

User avatar
sparkyb
Posts: 1091
Joined: Thu Sep 06, 2007 7:30 pm UTC
Location: Camberville proper!
Contact:

Re: Non-blocking Python

Postby sparkyb » Mon Nov 03, 2008 6:04 pm UTC

Kerberos wrote:
Berengal wrote:Just using del won't work. The problem can be fixed by making sure no thread ever blocks...

Aw, man. Now I have to code in an organized, logical manner. Life sucks.


Concurrency (threading and synchronization) is (imo) one of the more difficult topics to fully understand and deal with in programming. However, if it invaluably useful. I believe in the importance of doing it The Right WayTM, but I learned about it in a class so I'm not sure I can recommend a good online/print way to learning it. Can anyone else? But you're right, it isn't going to be fun.

As far as the issue goes (which others have mostly covered), you can't delete a thread with del (or any other method from the outside). The problem is not memory leak or thread leak that I'm warning you about. When a process ends, all of its memory is automatically reclaimed and any remaining threads are ended. However, this assumes the process does end. The problem I have had is that sometimes, despite calling sys.exit() in your main thread, the process will not end so long as there is another thread with a blocked IO call. Instead it will just hang. Now, here's the way I typically handle being able to stop threads:

Spoiler:

Code: Select all

from threading import Thread
import atexit

class IOThread(Thread):
    def __init__(self,sock):
        Thread.__init__(self)
       
        self.__stopping = False
        self.sock = sock
       
        atexit.register(self.stop)
    # end __init__
   
    def stop(self):
        self.__stopping = True
    # end stop
   
    def run(self):
        self.sock.settimeout(10)
        buffer = ""
        while (not self.__stopping):
            try:
                data = self.sock.recv(1024)
            except socket.timeout:
                continue
            except socket.error:
                break
           
            buffer+=data
           
            while ('\n' in buffer):
                i = buffer.index('\n')+1
                msg = buffer[:i]
                buffer = buffer[i:]
                self.handleMessage(msg)
       
        try:
            self.sock.close()
        except:
            pass
    # end run


So now you can effectively stop the thread by calling .stop(). However, the thread will non necessarily stop immediately and .stop() won't wait for the thread to end before it returns (you can call Thread.join() after, or in stop, to wait for it to actually finish). It will only stop when it comes around the while loop in the run function. This is why it is important to have a timeout on the socket so that it won't just hang on sock.recv() forever (if no more input comes). The timeout insures that it goes around the loop and checks the stop condition at least once every 10 seconds.

You can now stop the thread any time you don't want it anymore by calling .stop(). You can also make sure that you call .stop() before you try to end the program with sys.exit(). However, sometimes I worry that I'll forget to call .stop() first or that my program will end due to an uncaught exception (when I can't know to call .stop() first) rather a deliberate exit (before all the bugs are worked out). So one thing that I'll sometimes do (which I did in this example), is use the atexit module to register that the .stop() function should always be called before the process tries to exit.

To your question about what happens when you when you perform operations on a socket with a timeout, the timeout is per operation. So if you had a seven second timeout and your input came after 5 seconds the recv function would return after 5 seconds with that input. It would go around the loop and then wait for input again. If you got input after another 5 seconds it would return then with that input and go around the loop again. If the next input would come after 10 seconds, it would raise a timeout exception after 7 seconds, be caught, go around the loop, wait for input again, and then after 3 more seconds the input would come and it would return.

I don't ever trust recv to return with exactly a full message because a message could have been split between multiple packets or a single packet could have multiple messages grouped together. I always use either some kind of message separator (as in this example) or some message format that I can read byte by byte to know when I'm done (for instance if the first byte was the length, I could try and read at least one byte and once I have one I could wait until I get that many more to process the message). Since recv will not necessarily return the right amount of data, I always keep a buffer of received data and remove and process messages once I have enough.

jimrandomh
Posts: 110
Joined: Sat Feb 09, 2008 7:03 am UTC
Contact:

Re: Non-blocking Python

Postby jimrandomh » Tue Nov 04, 2008 3:34 am UTC

The function you are looking for is 'select'; I'm surprised no one's mentioned it. Threads will work, but they carry a heavy price in both complexity (they're famously hard to debug) and system resources. Setting a timeout works but only poorly, because it forces you into a tradeoff between unnecessary latency and unnecessary wasted processor time. With select(), you provide a list of all the sockets and file descriptors you're interested in, and it blocks on all of them at once, then gives you a list of which ones are ready.

User avatar
Kerberos
Posts: 189
Joined: Sun Oct 21, 2007 1:41 am UTC
Location: Male

Re: Non-blocking Python

Postby Kerberos » Tue Nov 04, 2008 3:52 am UTC

Where is this "select"? I can't find it in the socket module or object, or threading, or threading.Thread.

Thanks for the explanation, sparkyb. But again, where is the ".stop" function? I can't find it, looking in the places I mentioned or in the sys module.

And Jem, thank you for the code, as well. What is the "join" method? It seems like you're passing it a number?

User avatar
sparkyb
Posts: 1091
Joined: Thu Sep 06, 2007 7:30 pm UTC
Location: Camberville proper!
Contact:

Re: Non-blocking Python

Postby sparkyb » Tue Nov 04, 2008 4:34 am UTC

Kerberos wrote:Where is this "select"? I can't find it in the socket module or object, or threading, or threading.Thread.

Thanks for the explanation, sparkyb. But again, where is the ".stop" function? I can't find it, looking in the places I mentioned or in the sys module.

And Jem, thank you for the code, as well. What is the "join" method? It seems like you're passing it a number?


the .stop() method I was talking about was the one I wrote on my IOThread class in my spoilered example. Thread.join waits until the thread has terminated before returning. The optional parameter is a timeout in seconds. If specified, Thread.join will return after that many seconds even if the thread has not yet ended. To tell if the thread actually ended or the join call just timed-out you have to call Thread.isAlive().

jimrandomh wrote:The function you are looking for is 'select'; I'm surprised no one's mentioned it. Threads will work, but they carry a heavy price in both complexity (they're famously hard to debug) and system resources. Setting a timeout works but only poorly, because it forces you into a tradeoff between unnecessary latency and unnecessary wasted processor time. With select(), you provide a list of all the sockets and file descriptors you're interested in, and it blocks on all of them at once, then gives you a list of which ones are ready.


First of all, the select module is not cross platform safe. Secondly, the advantage of select is that it allows blocking on multiple objects at the same time returning as soon as any have input ready, however this is only an acceptable solution if all of the things you want to wait on can we passed to select. In this case I think you can select on both a socket and sys.in under Linux, so this might work (I'm not certain), but you can't select on sys.in under Windows it wouldn't work. And there are other circumstances where you want to wait for input on a socket and keep the main thread running where select wouldn't help. You're right that a timeout alone in a single thread does have the trade off between latency (too long a timeout) and processor spinning (to short a timeout), however this is why it should be coupled with threading. In a thread with a fairly long timeout you only incur latency when trying to stop the thread and a few extra loops ever few seconds isn't a great tax of computation. Concurrency is a fundamental part of modern programming and and important technique to have in your toolbox. I disagree with your implication that they should be avoided.

User avatar
jemthealmighty
Posts: 145
Joined: Fri May 23, 2008 6:55 am UTC
Location: Dubbo, NSW, Australia
Contact:

Re: Non-blocking Python

Postby jemthealmighty » Tue Nov 04, 2008 6:17 am UTC

Kerberos wrote:And Jem, thank you for the code, as well. What is the "join" method? It seems like you're passing it a number?

As Sparky said, it is a timeout before the thread 'joins' back with its parent.

User avatar
sparkyb
Posts: 1091
Joined: Thu Sep 06, 2007 7:30 pm UTC
Location: Camberville proper!
Contact:

Re: Non-blocking Python

Postby sparkyb » Tue Nov 04, 2008 1:42 pm UTC

jemthealmighty wrote:As Sparky said, it is a timeout before the thread 'joins' back with its parent.


I hope that's not what I said, because that doesn't sound right to me. It is a timeout of the maximum amount of time to wait for a thread to join back with its parent. It may join sooner than that number (or already be joined and return instantly) or it make take longer than that time or never join but the join method will still return after that amount of time instead of hanging longer or indefinitely. And join is a fancy (and unimportant) way of saying that it has ended (its .run() method has returned). But the important point is that join() does not cause this to happen (the target thread to end), it simply blocks the calling thread waiting until it does happen, so you better have done something just before to signal the other thread that you'd like it to end before you wait for it.

User avatar
Kerberos
Posts: 189
Joined: Sun Oct 21, 2007 1:41 am UTC
Location: Male

Re: Non-blocking Python

Postby Kerberos » Sat Nov 08, 2008 5:49 pm UTC

Thanks for the help, everyone. I now apparently understand sockets and threading well enough to write a rudimentary chat program.

One quick (I hope) question that probably isn't threadworthy, and is relevant in that it pertains to the chat program I wrote: if you're in the middle of typing something when the other person enters a message, what he said gets added onto what you're typing. Is there any easy way to (a) bump down the line you're typing on when you receive input from the other person or (b) make your cursor always at the bottom of the screen?

User avatar
sparkyb
Posts: 1091
Joined: Thu Sep 06, 2007 7:30 pm UTC
Location: Camberville proper!
Contact:

Re: Non-blocking Python

Postby sparkyb » Mon Nov 10, 2008 12:21 am UTC

Kerberos wrote:One quick (I hope) question that probably isn't threadworthy, and is relevant in that it pertains to the chat program I wrote: if you're in the middle of typing something when the other person enters a message, what he said gets added onto what you're typing. Is there any easy way to (a) bump down the line you're typing on when you receive input from the other person or (b) make your cursor always at the bottom of the screen?


Sounds like you want some kind of curses interface where you draw your input in a different, unscrolling part of of the screen than where you show the dialog. I haven't really done too much like that so I can't really help you out. It isn't the most platform independent thing in the world. It doesn't seem to work at all on Windows for me, which is where I spend most of my (programming) time these days. There are of course other curses libraries you can download besides the one in the standard library. I've used one before which worked on Windows (probably only Windows, though). I'll leaving searching for a suitable implementation for your needs as an exercise for the reader.

User avatar
Berengal
Superabacus Mystic of the First Rank
Posts: 2707
Joined: Thu May 24, 2007 5:51 am UTC
Location: Bergen, Norway
Contact:

Re: Non-blocking Python

Postby Berengal » Mon Nov 10, 2008 10:55 am UTC

Another option is to just use a regular GUI library. In it's simplest form, this could just be two text fields (one for input and one for output) and a button ("send").
It is practically impossible to teach good programming to students who are motivated by money: As potential programmers they are mentally mutilated beyond hope of regeneration.

User avatar
Kerberos
Posts: 189
Joined: Sun Oct 21, 2007 1:41 am UTC
Location: Male

Re: Non-blocking Python

Postby Kerberos » Tue Nov 11, 2008 1:22 am UTC

Okay, thank you. Is there a good GUI system for Python somewhere? I may have tried something called XWindows a while ago, but that might've been for C.

User avatar
Berengal
Superabacus Mystic of the First Rank
Posts: 2707
Joined: Thu May 24, 2007 5:51 am UTC
Location: Bergen, Norway
Contact:

Re: Non-blocking Python

Postby Berengal » Tue Nov 11, 2008 2:26 am UTC

Python comes with TKInter built-in, which means any computer that can run python and has GUI support will run your program. Other than that, there's also python bindings for many other GUIs readily available, but I've done too little GUI work, and almost none in python, to make a recommendation.
It is practically impossible to teach good programming to students who are motivated by money: As potential programmers they are mentally mutilated beyond hope of regeneration.

User avatar
thoughtfully
Posts: 2253
Joined: Thu Nov 01, 2007 12:25 am UTC
Location: Minneapolis, MN
Contact:

Re: Non-blocking Python

Postby thoughtfully » Tue Nov 11, 2008 8:08 pm UTC

Tkinter is great for light work and beginners. I use GTK mostly now, but I still use Tkinter for simpler stuff, to keep up my familiarity with it. Its simple, easy to learn, and very portable. And a gawdawfuly less bloated than the popular GUI toolkits, GTK/QT/wx. Fltk and Fox are still light though :)

One thing to keep in mind with any GUI system is that GUIs are huge globs of state, and are generally not thread-safe under the hood. You have to do all GUI calls in a single thread (usually the main thread). You can also setup timeouts in pretty much any GUI and poll stuff that way, without threads.
Image
Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.
-- Antoine de Saint-Exupery

User avatar
sparkyb
Posts: 1091
Joined: Thu Sep 06, 2007 7:30 pm UTC
Location: Camberville proper!
Contact:

Re: Non-blocking Python

Postby sparkyb » Thu Nov 13, 2008 3:25 am UTC

I don't want to start a religious war, but my preference is for wxPython (the Python binding for wxWidgets) over Tkinter. I used to do some simple things with Tk, but couldn't never quite get into its way of doing thing. I could never really remember how to work with it and it always ended up with ugly looking GUIs. wxWidgets seems to be quite popular now and is the GUI for a number of open source project that I use (like Audacity). It took me a little time to wrap my head around it (no longer than any other GUI toolkit), but now I am very pleased with it and my ability to use it to do things in a pretty pythonic way.


Return to “Coding”

Who is online

Users browsing this forum: No registered users and 6 guests