Getting lazy with decorators

Discussion in 'Python' started by Josh English, Jun 24, 2012.

  1. Josh English

    Josh English Guest

    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?
     
    Josh English, Jun 24, 2012
    #1
    1. Advertising

  2. Josh English

    Peter Otten Guest

    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()
     
    Peter Otten, Jun 24, 2012
    #2
    1. Advertising

  3. 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 H. Holek, Jun 24, 2012
    #3
  4. Josh English

    Josh English Guest

    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?
     
    Josh English, Jun 25, 2012
    #4
  5. Josh English

    Josh English Guest

    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?
     
    Josh English, Jun 25, 2012
    #5
  6. Josh English

    Peter Otten Guest

    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()
     
    Peter Otten, Jun 26, 2012
    #6
  7. Josh English

    Peter Otten Guest

    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").
     
    Peter Otten, Jun 26, 2012
    #7
  8. Josh English

    Josh English Guest

    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
     
    Josh English, Jun 28, 2012
    #8
  9. Josh English

    Josh English Guest

    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
     
    Josh English, Jun 28, 2012
    #9
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    6
    Views:
    501
    Joe Smith
    Apr 21, 2004
  2. Arien Malec

    PEP 318 decorators are not Decorators

    Arien Malec, Aug 13, 2004, in forum: Python
    Replies:
    11
    Views:
    587
    Arien Malec
    Aug 16, 2004
  3. Ken Pu
    Replies:
    3
    Views:
    695
    Steven D'Aprano
    Jan 16, 2009
  4. Boris Borcic
    Replies:
    0
    Views:
    570
    Boris Borcic
    Jan 16, 2009
  5. Boris Borcic
    Replies:
    0
    Views:
    566
    Boris Borcic
    Jan 16, 2009
Loading...

Share This Page