In this post we show how to use the getattr built-in, why and where it is worth using it. It is an introductory post and only a minimal python knowledge is requied.
In fact it could be interesting even to those who don’t know Python, since it shows some advantages of a dynamic programming language.
I think that every python programmer sooner or later wrote some legacy
code that resembled Cmd module.
If you don’t know Cmd module you should take a look at it, even if it’s not necessary to understand this article.
At least sooner or later you may have had the necessity to call a method on an object depending on some user input/network input/gui input. Think about a line based server: a popserver or an smtp server, for example. The client send strings and the first part of the string is the “command” to be executed. Depending on which “command” the user chose, the proper function has to be called.
Let’s think about a POP server. It is a simple example, it has few commands and I’m quite familiar with it (since I’m writing one). Of course you can think about some “menu based” textual application
Anyway… there are several POP commands: RETR, TOP, USER, PASS, QUIT, DELE, etc.
Of course you could write something like (of course this would be in a “POPServer” object)
# pop commands are never longer than 4 characters command = cline[:4] if command=="USER": self.do_user(cline) elif command=="PASS": self.do_pass(cline) ... else: self.answer("-ERR Invalid command")
This would be a very unpythonic way to code. You can avoid a lot of useless typing using getattr built-in
getattr(object, “name”) returns the attribute named “name” from object object. This is simple and effective. Of course it follows usual python name resolution and
getattr(object, "name")(arg1, ..., argn)
if the same as
object.name(arg1, ..., argn)
(ellipses are not part of python code 😉 )
In fact if object has no “name” attribute, AttributeError is raised. And of course nobody would use the getattr form unless “name” is not a literal string but something known only at runtime (ok, in python nothing is known at compile time… but you got what I meant).
I think at this point everybody understood how we can use getattr to make above code more readable. The first thing we would write would be
command= cline[:4] try: getattr(self, "do_" + command)(cline) except AttributeError: self.answer("-ERR Invalid command")
This code is almost correct. It is quite secure (if you do not name methods that should not be called by the client with a name starting with “do_”) and works as expected, but has a major drawback.
If some code inside the “do_command” method raises an AttributeError (or an exception that is a subclass of AttributeError) this exception is caught even if it should not.
In fact if you do a lookup in a dictionary and you don’t find the key, you get a KeyError, subclass of AttributeError and it is caught, even if the server should have crashed. In fact debugging becomes a mess: you do have no informations on where the exceptions generates.
1+1=2: the solution
You could be tempted to use a hasattr before getattr instead of the try block. But remember Python philosophy about exceptions: EAFP (it’s easier to ask forgiveness than permission). That is to say, try to do what you want to do and if you were not allowed fix it.
In some other languages it is more common the LBYL approach (look before you leap/check before doing something if you were allowed): unfortunately it has some disadvantages both for readability and performance. Still in other languages, as I said, LBYL is the more common and “correct” way to proceed.
So the code becomes
command= cline[:4] try: action = getattr(self, "do_" + command) except AttributeError: self.answer("-ERR Invalid command") else: action(cline)
An else clause is executed if there were no exceptions and is outside
the try block, so exceptions are free to propagate.
Many of the ideas in this post come from the reading of Python in a Nutshell chapter about exceptions. I think every python programmer should have that book on his desk.