![]() |
Getting lazy with decorators
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? |
Re: Getting lazy with decorators
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() |
Re: Getting lazy with decorators
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 stefan@epy.co.at |
Re: Getting lazy with decorators
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? |
Re: Getting lazy with decorators
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? |
Re: Getting lazy with decorators
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() |
Re: Getting lazy with decorators
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"). |
Re: Getting lazy with decorators
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 |
Re: Getting lazy with decorators
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 |
| All times are GMT. The time now is 12:27 PM. |
Powered by vBulletin®. Copyright ©2000 - 2013, vBulletin Solutions, Inc.
SEO by vBSEO ©2010, Crawlability, Inc.