Learning to use decorators with classes

Bruno Desthuilliers bruno.42.desthuilliers at websiteburo.invalid
Tue Jun 30 08:48:23 EDT 2009

Mr SZ a écrit :
> Hi,
> I'm writing an LDAP plugin for my TG2 application. In this I wrote a small class based decorator with args to set up a connection and call the necessary
> functionality but I'm having problems with it. Here's my code:
(snip code)
> class LdapPlugin(Plugin):
>     ...
>     def __init__(self, **kw):
>         Plugin.__init__(self)
>     @getConnection(self._settings, self.__cred__)

Don't use '__name__', they are reserved for the implementation. And 
FWIW, don't use '__name' unless you have a really compelling reason to 
do so.

>     def search(self, **kw):
>         print 'Searching'
>         ...

This can't work, and it's a FAQ FWIW - but since there's no official 
c.l.py FAQ, we won't hold it against you !-)

def and class are both *executable* statements (yes, classes and 
functions creation - like almost anything in Python - are run-time 

The first one creates a function object - *wherever* it happens - and 
bind the function object to the function's name in the current 
namespace. Think of it as an equivalent of the following javascript snippet:

    var func_name = function(arg) { /* function's body */ };

The second statement - class - builds a class object from the names 
defined in the class statement's body (that is, names defined in the 
class statement's body will become attributes of the class object).

Now about the 'methods' and 'self' stuff...

First understand that there's *nothing* magical with 'self'. It's *not* 
a keyword. It's only a naming convention, and you could use any legal 
Python identified instead.

The reason we have to explicitly mention it as first argument of the 
function is that it's the only way the function's body can get access to 
the current instance. What happens is (overly simplified) that during 
attribute resolution, when the found attribute happens to be a function, 
  this function is wrapped - together with the instance on which the 
attribute was looked up and it's class -  into a callable method object. 
Then when you call this method object, it inserts the instance as first 
argument to the function call, and returns the result of the function 
call (if you want to read more about this and how computed attributes 
are implemented in Python, google for 'descriptor protocol').

IOW, and to make a long story short, calling instance.method is the same 
as calling Class.method(instance).

Ok, now to the point: when you call getConnection within the class 
statement's body, there's no magical "self" keyword poiting to an 
instance, and since the class itself doesn't yet exists, so there's *no* 
way you could get at an instance of it anyway !-)

There are many ways to solve your problem, the simplest bing probably to 
write another decorator calling on the first one, ie:

def connected_method(func):
     def connected(self, *args, **kw):
         wrapped = getConnection(self.__this, self.__that)(func)
         return wrapped(*args, **kw)

     return connected

Note that this may not be that efficient - you'll have quite a few 
function calls involved here.

While we're at it, a couple comments on your code... First, please read 
pep08 (naming and coding conventions) on python.org. Conventions are 
very important in Python.

wrt/ error handling:

                 if tls:
                 if anon:
                     con.simple_bind_s(dn, pw)

             except ldap.LDAPError, e:
                 print e.message['info']

This kind of "error handling" is more than useless - it's worse than no 
error handling at all. If you cannot handle the problem (I really mean 
*handle*, you know, like do something to fix it), just let the exception 
propagate - you'll get a nice traceback with all the necessary debugging 
informations. Users of your package can always add a top-level 
"catch-all" exception handler that will log tracebacks, send alert mails 
to the team, and present the end-user with a nicely formatted error 
message (and even possibly a way to handle the problem - like providing 
appropriate connection data (credentials, url, whatever) !-)

Also note that sys.stdout is for *normal* program outputs. Error 
messages belongs to sys.stderr.

                 kw['conn'] = connection
                     f(*args, **kw)
                 except Exception, e:
                     print 'Exception in Plugin execution: %s' % e

Same as above : if you can't handle the exception, leave it alone.


More information about the Python-list mailing list