Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #24378 > unrolled thread

Getting lazy with decorators

Started byJosh English <Joshua.R.English@gmail.com>
First post2012-06-23 18:58 -0700
Last post2012-06-24 12:44 +0200
Articles 9 — 3 participants

Back to article view | Back to comp.lang.python


Contents

  Getting lazy with decorators Josh English <Joshua.R.English@gmail.com> - 2012-06-23 18:58 -0700
    Re: Getting lazy with decorators Peter Otten <__peter__@web.de> - 2012-06-24 10:07 +0200
      Re: Getting lazy with decorators Josh English <Joshua.R.English@gmail.com> - 2012-06-25 13:04 -0700
        Re: Getting lazy with decorators Peter Otten <__peter__@web.de> - 2012-06-26 08:57 +0200
          Re: Getting lazy with decorators Josh English <Joshua.R.English@gmail.com> - 2012-06-27 16:09 -0700
          Re: Getting lazy with decorators Josh English <Joshua.R.English@gmail.com> - 2012-06-27 16:09 -0700
        Re: Getting lazy with decorators Peter Otten <__peter__@web.de> - 2012-06-26 09:03 +0200
      Re: Getting lazy with decorators Josh English <Joshua.R.English@gmail.com> - 2012-06-25 13:04 -0700
    Re: Getting lazy with decorators "Stefan H. Holek" <stefan@epy.co.at> - 2012-06-24 12:44 +0200

#24378 — Getting lazy with decorators

FromJosh English <Joshua.R.English@gmail.com>
Date2012-06-23 18:58 -0700
SubjectGetting lazy with decorators
Message-ID<9d0c01f4-4430-4a45-8776-20d8dede9e14@googlegroups.com>
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?

[toc] | [next] | [standalone]


#24382

FromPeter Otten <__peter__@web.de>
Date2012-06-24 10:07 +0200
Message-ID<mailman.1442.1340525279.4697.python-list@python.org>
In reply to#24378
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()

[toc] | [prev] | [next] | [standalone]


#24442

FromJosh English <Joshua.R.English@gmail.com>
Date2012-06-25 13:04 -0700
Message-ID<513d25ea-e977-4b84-b388-bfb23c171aae@googlegroups.com>
In reply to#24382
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?

[toc] | [prev] | [next] | [standalone]


#24461

FromPeter Otten <__peter__@web.de>
Date2012-06-26 08:57 +0200
Message-ID<mailman.1510.1340693868.4697.python-list@python.org>
In reply to#24442
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()

[toc] | [prev] | [next] | [standalone]


#24558

FromJosh English <Joshua.R.English@gmail.com>
Date2012-06-27 16:09 -0700
Message-ID<mailman.1572.1340838595.4697.python-list@python.org>
In reply to#24461
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

[toc] | [prev] | [next] | [standalone]


#24559

FromJosh English <Joshua.R.English@gmail.com>
Date2012-06-27 16:09 -0700
Message-ID<3e726c11-f3ee-4cd7-983c-cd4ab923a0b1@googlegroups.com>
In reply to#24461
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

[toc] | [prev] | [next] | [standalone]


#24462

FromPeter Otten <__peter__@web.de>
Date2012-06-26 09:03 +0200
Message-ID<mailman.1511.1340694230.4697.python-list@python.org>
In reply to#24442
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").

[toc] | [prev] | [next] | [standalone]


#24443

FromJosh English <Joshua.R.English@gmail.com>
Date2012-06-25 13:04 -0700
Message-ID<mailman.1496.1340654681.4697.python-list@python.org>
In reply to#24382
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?

[toc] | [prev] | [next] | [standalone]


#24390

From"Stefan H. Holek" <stefan@epy.co.at>
Date2012-06-24 12:44 +0200
Message-ID<mailman.1453.1340535958.4697.python-list@python.org>
In reply to#24378
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

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web