Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Python > Getting lazy with decorators

Reply
Thread Tools

Getting lazy with decorators

 
 
Josh English
Guest
Posts: n/a
 
      06-24-2012
I'm creating a cmd.Cmd class, and I have developed a helper method to easily handle help_xxx methods.

I'm trying to figure out if there is an even lazier way I could do this with decorators.

Here is the code:
*********************
import cmd


def add_help(func):
if not hasattr(func, 'im_class'):
return func #probably should raise an error
cls = func.im_class
setattr(cls, func.im_func.__name__.replace("do","help"), None)

return func


class BaseCmd(cmd.Cmd):
def __init__(self, *args, **kwargs):
cmd.Cmd.__init__(self, *args, **kwargs)

def show_help(self, func):
print "\n".join((line.strip() for line in func.__doc__.splitlines()))

@add_help
def do_done(self, line):
"""done
Quits this and goes to higher level or quits the application.
I mean, what else do you expect?
"""
return True

if __name__=='__main__':
c = BaseCmd()

print c.help_done


*********************

This generates "AttributeError: BaseCmd instance has no attribute 'help_done'"

The show_help method is the shortcut I want to use (I'm pretty sure it's from Doug Hellman's site). I'm wondering if it's possible to use a decorator such as add_help to automatically create the appropriate help_xxx function.

In the decorator, I can get the function and the name of the class, but I can't find the instance of the class that the method is attached to. Maybe this is just one step of lazy too far.


Am I right in thinking that I can't do this? There is no way to access the class instance from the method?
 
Reply With Quote
 
 
 
 
Peter Otten
Guest
Posts: n/a
 
      06-24-2012
Josh English wrote:

> I'm creating a cmd.Cmd class, and I have developed a helper method to
> easily handle help_xxx methods.
>
> I'm trying to figure out if there is an even lazier way I could do this
> with decorators.
>
> Here is the code:
> *********************
> import cmd
>
>
> def add_help(func):
> if not hasattr(func, 'im_class'):
> return func #probably should raise an error
> cls = func.im_class
> setattr(cls, func.im_func.__name__.replace("do","help"), None)
>
> return func
>
>
> class BaseCmd(cmd.Cmd):
> def __init__(self, *args, **kwargs):
> cmd.Cmd.__init__(self, *args, **kwargs)
>
> def show_help(self, func):
> print "\n".join((line.strip() for line in
> func.__doc__.splitlines()))
>
> @add_help
> def do_done(self, line):
> """done
> Quits this and goes to higher level or quits the application.
> I mean, what else do you expect?
> """
> return True
>
> if __name__=='__main__':
> c = BaseCmd()
>
> print c.help_done
>
>
> *********************
>
> This generates "AttributeError: BaseCmd instance has no attribute
> 'help_done'"
>
> The show_help method is the shortcut I want to use (I'm pretty sure it's
> from Doug Hellman's site). I'm wondering if it's possible to use a
> decorator such as add_help to automatically create the appropriate
> help_xxx function.
>
> In the decorator, I can get the function and the name of the class, but I
> can't find the instance of the class that the method is attached to.
> Maybe this is just one step of lazy too far.
>
>
> Am I right in thinking that I can't do this? There is no way to access the
> class instance from the method?


You cannot access a class instance because even the class itself doesn't
exist yet. You could get hold of the class namespace with sys._getframe(),

def add_help(f):
exec """\
def help_%s(self):
f = getattr(self, %r)
self.show_help(f)
""" % (f.__name__[3:], f.__name__) in sys._getframe(1).f_locals
return f

but here's a simpler approach:

import cmd

def add_help(f):
def help(self):
self.show_help(f)
f.help = help
return f


class BaseCmd(cmd.Cmd):
def __init__(self, *args, **kwargs):
cmd.Cmd.__init__(self, *args, **kwargs)

def show_help(self, func):
print "\n".join((line.strip() for line in
func.__doc__.splitlines()))

def __getattr__(self, name):
if name.startswith("help_"):
helpfunc = getattr(self, "do_" + name[5:]).help
setattr(self.__class__, name, helpfunc)
return getattr(self, name)
raise AttributeError

@add_help
def do_done(self, line):
"""done
Quits this and goes to higher level or quits the application.
I mean, what else do you expect?
"""
return True

if __name__=='__main__':
c = BaseCmd()
c.cmdloop()


 
Reply With Quote
 
 
 
 
Stefan H. Holek
Guest
Posts: n/a
 
      06-24-2012
On 24.06.2012, at 03:58, Josh English wrote:

> I'm creating a cmd.Cmd class, and I have developed a helper method to easily handle help_xxx methods.


When I need custom help processing I tend to simply override do_help().

Stefan

<plug>
http://pypi.python.org/pypi/kmd
</plug>

--
Stefan H. Holek
http://www.velocityreviews.com/forums/(E-Mail Removed)

 
Reply With Quote
 
Josh English
Guest
Posts: n/a
 
      06-25-2012
On Sunday, June 24, 2012 1:07:45 AM UTC-7, Peter Otten wrote:
>
> You cannot access a class instance because even the class itself doesn't
> exist yet. You could get hold of the class namespace with sys._getframe(),
>
> def add_help(f):
> exec """\
> def help_%s(self):
> f = getattr(self, %r)
> self.show_help(f)
> """ % (f.__name__[3:], f.__name__) in sys._getframe(1).f_locals
> return f
>
> but here's a simpler approach:
>
> import cmd
>
> def add_help(f):
> def help(self):
> self.show_help(f)
> f.help = help
> return f
>
>
> class BaseCmd(cmd.Cmd):
> def __init__(self, *args, **kwargs):
> cmd.Cmd.__init__(self, *args, **kwargs)
>
> def show_help(self, func):
> print "\n".join((line.strip() for line in
> func.__doc__.splitlines()))
>
> def __getattr__(self, name):
> if name.startswith("help_"):
> helpfunc = getattr(self, "do_" + name[5:]).help
> setattr(self.__class__, name, helpfunc)
> return getattr(self, name)
> raise AttributeError
>
> @add_help
> def do_done(self, line):
> """done
> Quits this and goes to higher level or quits the application.
> I mean, what else do you expect?
> """
> return True
>
> if __name__=='__main__':
> c = BaseCmd()
> c.cmdloop()



Okay. If I understand this, you are adding a help attribute to the class method. The help attribute is itself a function.

There is nothing in the documentation (that I have found) that points to this solution. Even after reading the do_help method in the cmd.Cmd source, I don't see this as working.

Yet it works.

How?
 
Reply With Quote
 
Josh English
Guest
Posts: n/a
 
      06-25-2012
On Sunday, June 24, 2012 1:07:45 AM UTC-7, Peter Otten wrote:
>
> You cannot access a class instance because even the class itself doesn't
> exist yet. You could get hold of the class namespace with sys._getframe(),
>
> def add_help(f):
> exec """\
> def help_%s(self):
> f = getattr(self, %r)
> self.show_help(f)
> """ % (f.__name__[3:], f.__name__) in sys._getframe(1).f_locals
> return f
>
> but here's a simpler approach:
>
> import cmd
>
> def add_help(f):
> def help(self):
> self.show_help(f)
> f.help = help
> return f
>
>
> class BaseCmd(cmd.Cmd):
> def __init__(self, *args, **kwargs):
> cmd.Cmd.__init__(self, *args, **kwargs)
>
> def show_help(self, func):
> print "\n".join((line.strip() for line in
> func.__doc__.splitlines()))
>
> def __getattr__(self, name):
> if name.startswith("help_"):
> helpfunc = getattr(self, "do_" + name[5:]).help
> setattr(self.__class__, name, helpfunc)
> return getattr(self, name)
> raise AttributeError
>
> @add_help
> def do_done(self, line):
> """done
> Quits this and goes to higher level or quits the application.
> I mean, what else do you expect?
> """
> return True
>
> if __name__=='__main__':
> c = BaseCmd()
> c.cmdloop()



Okay. If I understand this, you are adding a help attribute to the class method. The help attribute is itself a function.

There is nothing in the documentation (that I have found) that points to this solution. Even after reading the do_help method in the cmd.Cmd source, I don't see this as working.

Yet it works.

How?
 
Reply With Quote
 
Peter Otten
Guest
Posts: n/a
 
      06-26-2012
Josh English wrote:

> On Sunday, June 24, 2012 1:07:45 AM UTC-7, Peter Otten wrote:
>>
>> You cannot access a class instance because even the class itself doesn't
>> exist yet. You could get hold of the class namespace with
>> sys._getframe(),
>>
>> def add_help(f):
>> exec """\
>> def help_%s(self):
>> f = getattr(self, %r)
>> self.show_help(f)
>> """ % (f.__name__[3:], f.__name__) in sys._getframe(1).f_locals
>> return f
>>
>> but here's a simpler approach:
>>
>> import cmd
>>
>> def add_help(f):
>> def help(self):
>> self.show_help(f)
>> f.help = help
>> return f
>>
>>
>> class BaseCmd(cmd.Cmd):
>> def __init__(self, *args, **kwargs):
>> cmd.Cmd.__init__(self, *args, **kwargs)
>>
>> def show_help(self, func):
>> print "\n".join((line.strip() for line in
>> func.__doc__.splitlines()))
>>
>> def __getattr__(self, name):
>> if name.startswith("help_"):
>> helpfunc = getattr(self, "do_" + name[5:]).help
>> setattr(self.__class__, name, helpfunc)
>> return getattr(self, name)
>> raise AttributeError
>>
>> @add_help
>> def do_done(self, line):
>> """done
>> Quits this and goes to higher level or quits the application.
>> I mean, what else do you expect?
>> """
>> return True
>>
>> if __name__=='__main__':
>> c = BaseCmd()
>> c.cmdloop()

>
>
> Okay. If I understand this, you are adding a help attribute to the class
> method. The help attribute is itself a function.
>
> There is nothing in the documentation (that I have found) that points to
> this solution.


That's because I "invented" it.

@deco
def func(...): ...

is equivalent to

def func(...): ...
func = deco(func)

so only one assignment is ever made in the enclosing namespace. If you have
more targets you have to put them somewhere else. Making the help_xxx()
function an attribute of do_xxx() seemed the obvious choice.

> Even after reading the do_help method in the cmd.Cmd
> source, I don't see this as working.


do_help(name)

looks up a "help_" + name method and invokes it if that lookup succeeds.

> Yet it works.


In the example above do_help() looks for a help_done attribute which doesn't
exist. As a fallback the __getattr__() method is invoked with the
"help_done" argument, finds that the name starts with "help_" and proceeds
with

>> helpfunc = getattr(self, "do_" + name[5:]).help


i. e. it looks for getattr(self, "do_help") which does exist and then stores
its help attribute (if that doesn't exist an AttributeError is implicitly
raised) in helpfunc. helpfunc is then added as "help_done" to the class

>> setattr(self.__class__, name, helpfunc)


and looked up again in the instance:

>> return getattr(self, name)


This time it exists and from the class attribute help_done an instance
method is created, returned from __getattr__() and invoked by do_help().

PS: Stefan is probably right that you should just override do_help()

 
Reply With Quote
 
Peter Otten
Guest
Posts: n/a
 
      06-26-2012
Peter Otten wrote:

>>>helpfunc = getattr(self, "do_" + name[5:]).help

>
> i. e. it looks for getattr(self, "do_help") which does exist and then


Sorry that should be getattr(self, "do_done").


 
Reply With Quote
 
Josh English
Guest
Posts: n/a
 
      06-27-2012
On Monday, June 25, 2012 11:57:39 PM UTC-7, Peter Otten wrote:
> >
> > There is nothing in the documentation (that I have found) that points to
> > this solution.

>
> That's because I "invented" it.
>


Oh bother. The lines I completely overlooked were in your __getattr__ override.

Boy is my face red.

On further experimentation, adding a do_xxx command without the decorator still works...ish. The undecorated do_xxx is still considered to have a help function, and it prints the raw docstring (instead of using the show_help method to clean it up).

Josh
 
Reply With Quote
 
Josh English
Guest
Posts: n/a
 
      06-27-2012
On Monday, June 25, 2012 11:57:39 PM UTC-7, Peter Otten wrote:
> >
> > There is nothing in the documentation (that I have found) that points to
> > this solution.

>
> That's because I "invented" it.
>


Oh bother. The lines I completely overlooked were in your __getattr__ override.

Boy is my face red.

On further experimentation, adding a do_xxx command without the decorator still works...ish. The undecorated do_xxx is still considered to have a help function, and it prints the raw docstring (instead of using the show_help method to clean it up).

Josh
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
lazy evaluation is sometimes too lazy... help please. Ken Pu Python 3 01-16-2009 11:23 AM
Re: lazy evaluation is sometimes too lazy... help please. Boris Borcic Python 0 01-16-2009 10:46 AM
Re: lazy evaluation is sometimes too lazy... help please. Boris Borcic Python 0 01-16-2009 10:37 AM
hello! first post to clr. I'm asking about an attempt at a lazy rubysolution to computing fibonacci numbers for a project euler problem. seems tobe a bug in lazy ruby... tphyahoo Ruby 6 08-08-2008 08:15 PM
PEP 318 decorators are not Decorators Arien Malec Python 11 08-16-2004 06:38 PM



Advertisments