A programming puzzle re decorators

4 views
Skip to first unread message

Edward K. Ream

unread,
May 11, 2009, 2:00:59 PM5/11/09
to leo-editor
I'm working on moving the rst3 plugin into Leo's core. It's a
straightforward project.

Along the way, however, I thought I would experiment with using
decorators to register commands. The idea is to have the decorator
make an entry in c.commandsDict. Something like this:

class command(object):

'''A decorator to register a function/method as a command.'''

def __init__(self,name):
self.name = name

def __call__(self,func):
c.commandsDict[self.name] = func
return func # Use func as is, without any additional wrapping.

This would be used as follows:

@command('my-command-name)
def myCommand (self,event=None):
'''Return the set of all sets that are not members of
themselves.'''

But there is a hitch. The __init__ and __call__ methods are called at
"compile time", and at that time, neither c nor c.commandsDict exist.

At present, I have punted entirely. I hacked c.finishCreate to call
c.rstCommands.finishCreate, and that in turn updates c.commandsDict.

The only way I can see to make @command work would be to have the
__call__ method make entries in some module-level dict, say
leoCommands.commandsDict. Then c.finishCreate could copy the module-
level dict into c.commandsDict.

I'm not happy using module-level data structures, but using a module-
dict would be about equivalent to using the module-level classesList
list at the end of leoEditCommands.py, so perhaps the work-around is
no worse than what is in use now. OTOH, the classesList hack must be
considered one of the Leo's worst warts.

Can anyone else think of a better way?

Edward

Ville M. Vainio

unread,
May 11, 2009, 2:05:39 PM5/11/09
to leo-e...@googlegroups.com
On Mon, May 11, 2009 at 8:00 PM, Edward K. Ream <edre...@gmail.com> wrote:

> The only way I can see to make @command work would be to have the
> __call__ method make entries in some module-level dict, say
> leoCommands.commandsDict.  Then c.finishCreate could copy the module-
> level dict into c.commandsDict.
>
> I'm not happy using module-level data structures, but using a module-
> dict would be about equivalent to using the module-level classesList
> list at the end of leoEditCommands.py, so perhaps the work-around is
> no worse than what is in use now.  OTOH, the classesList hack must be
> considered one of the Leo's worst warts.
>
> Can anyone else think of a better way?

Something I've wondered for a long time - why do the commands need to
reside in the commander in the first place, as opposed to a global
dict (mapping string => callable) that's available everywhere? I don't
see the big benefit in having different commands available in
different leo documents...

--
Ville M. Vainio
http://tinyurl.com/vainio

Edward K. Ream

unread,
May 13, 2009, 8:52:38 AM5/13/09
to leo-e...@googlegroups.com
On Mon, May 11, 2009 at 1:05 PM, Ville M. Vainio <viva...@gmail.com> wrote:

Something I've wondered for a long time - why do the commands need to
reside in the commander in the first place, as opposed to a global
dict (mapping string => callable) that's available everywhere? I don't
see the big benefit in having different commands available in
different leo documents...

The present situation is a matter of historical accident.  The only requirement for any command, really, is that the assignment c = self (or c = self.c) can be made at the start of each command.  Thus, commands can reside anywhere so long as c is accessible.

c.commandsDict is the dict you mention.  It's best to have this dict in the commander, as each commander can, in fact, have its own separate set of commands.

Edward

Ville M. Vainio

unread,
May 21, 2009, 4:18:38 AM5/21/09
to leo-e...@googlegroups.com
On Mon, May 11, 2009 at 9:00 PM, Edward K. Ream <edre...@gmail.com> wrote:

> This would be used as follows:
>
> @command('my-command-name)
> def myCommand (self,event=None):
>     '''Return the set of all sets that are not members of
> themselves.'''
>
> But there is a hitch.  The __init__ and __call__ methods are called at
> "compile time", and at that time, neither c nor c.commandsDict exist.

I have some plans to unify what has been talked about in this thread.
I want to make it easy to create 'global' commands and buttons in
plugins. I just think the current way of having to hook to frame
creation is a bit too "involved" to remember easily (without
copy-pasting the stuff from somewhere)

So to create command you would do

import leoPlugins

def mycmd(c,p, event):
print c,p

leoPlugins.expose_command('my-command', mycmd)
leoPlugins.expose_button('press-this',mycmd)

These would be available on *all* new commanders, and the current
commander as well (for easy testing). Plugins would not have to
contain more code than what is presented above.

The idea is to have a global command dict, and global button dict.
There is one after-create-frame handler that introduces all the
entries in this dict to the commander command dict.

I think functions are clearer than decorators here, because leo
already uses @ character extensively and it can be a source of
confusion.

Thoughts?

Edward K. Ream

unread,
May 21, 2009, 8:41:03 AM5/21/09
to leo-e...@googlegroups.com
On Thu, May 21, 2009 at 3:18 AM, Ville M. Vainio <viva...@gmail.com> wrote:

> But there is a hitch.  The __init__ and __call__ methods are called at
> "compile time", and at that time, neither c nor c.commandsDict exist.

[snip]
 

So to create command you would do

import leoPlugins

def mycmd(c,p, event):
 print c,p

leoPlugins.expose_command('my-command', mycmd)
leoPlugins.expose_button('press-this',mycmd)

This is intriguing.

These would be available on *all* new commanders, and the current
commander as well (for easy testing). Plugins would not have to
contain more code than what is presented above. 

The idea is to have a global command dict, and global button dict.

The dict would be something like g.app.commandsDict, rather than the present c.commandsDict.

There is one after-create-frame handler that introduces all the
entries in this dict to the commander command dict.

Yes, this should work.

I think functions are clearer than decorators here, because leo
already uses @ character extensively and it can be a source of
confusion.

I would prefer to use a decorator, perhaps even a do-nothing decorator, that clearly marks code as a command.

Thoughts?

This is a truly great idea.  Let me think out loud for a few moments...

1.  This would create a new pattern in which commands are functions, not members of the commands class.  I suspect that methods can be a "function" in this sense.  This might be useful so that commands could share code.

2.  The new pattern could coexist with the old way of defining commands.  However, the idea of having all commands be available to all commands would suggest using this pattern for all of Leo's commands.

I don't really understand all the technical details, but Python being what it is, I find it hard to believe that there are any insuperable problems lurking anywhere.

Hmmm.  Does passing c explicitly to the command make c available to the decorator?  Probably not, but maybe it doesn't matter, since the entry would be made in g.app.commandsDict rather than c.commandsDict.  In other words, the decorator only needs g at module-load time, and that it has.

So I think this is an excellent idea in all respects.

Edward

Ville M. Vainio

unread,
May 21, 2009, 8:47:42 AM5/21/09
to leo-e...@googlegroups.com
On Thu, May 21, 2009 at 3:41 PM, Edward K. Ream <edre...@gmail.com> wrote:

>> The idea is to have a global command dict, and global button dict.
>
> The dict would be something like g.app.commandsDict, rather than the present
> c.commandsDict.

Exactly.

>> I think functions are clearer than decorators here, because leo
>> already uses @ character extensively and it can be a source of
>> confusion.
>
> I would prefer to use a decorator, perhaps even a do-nothing decorator, that
> clearly marks code as a command.

Well, that's just a matter of preference. We can do it as a decorator as well.

> I don't really understand all the technical details, but Python being what
> it is, I find it hard to believe that there are any insuperable problems
> lurking anywhere.

Yeah, it's a piece of cake to implement.

> Hmmm.  Does passing c explicitly to the command make c available to the
> decorator?  Probably not, but maybe it doesn't matter, since the entry would
> be made in g.app.commandsDict rather than c.commandsDict.  In other words,
> the decorator only needs g at module-load time, and that it has.

Yeah, the decorator just registers the command to g.app.commandsDict.
Only the actual code that executes the function (simulateCommand /
whatever) will need to pass the c.

> So I think this is an excellent idea in all respects.

Alrgiht, I'll implement it today unless you wan't to do it yourself :-).

Edward K. Ream

unread,
May 21, 2009, 9:35:18 AM5/21/09
to leo-e...@googlegroups.com
On Thu, May 21, 2009 at 7:47 AM, Ville M. Vainio <viva...@gmail.com> wrote:

Alrgiht, I'll implement it today unless you wan't to do it yourself :-).

Please go ahead.  As you may have noticed, I have a few bugs to fix.  And I need to delegate more :-)

Edward

Ville M. Vainio

unread,
May 21, 2009, 11:03:30 AM5/21/09
to leo-e...@googlegroups.com
On Mon, May 11, 2009 at 9:00 PM, Edward K. Ream <edre...@gmail.com> wrote:

> Along the way, however, I thought I would experiment with using
> decorators to register commands.  The idea is to have the decorator
> make an entry in c.commandsDict.  Something like this:

I have now pushed g.command decorator. It pushes stuff to
g.app.global_commands_dict, which gets copied to c on commander
creation. it's rev 1889, you'll note that it's a simple
implementation. I didn't add g.button yet.

Ville M. Vainio

unread,
May 21, 2009, 11:30:06 AM5/21/09
to leo-e...@googlegroups.com
On Thu, May 21, 2009 at 6:03 PM, Ville M. Vainio <viva...@gmail.com> wrote:

> I have now pushed g.command decorator. It pushes stuff to

Ah, stupid me, I didn't specify an example:

@g.command('bookmark')
def bookmark(event):
c = event.get('c')
p = c.currentPosition()
bookmarks.append(p.gnx)
g.es('bookmarked')

Now, if all your plugin did was specifying commands, you wouldn't need
to have anything more in the plugin module. In particular, you can
forego stuff like::

leoPlugins.registerHandler('after-create-leo-frame',onCreate)

Terry Brown

unread,
May 21, 2009, 11:51:46 AM5/21/09
to leo-e...@googlegroups.com
On Thu, 21 May 2009 07:41:03 -0500

"Edward K. Ream" <edre...@gmail.com> wrote:

[on decorators to indicate commands]

> 1. This would create a new pattern in which commands are functions,
> not members of the commands class. I suspect that methods can be a
> "function" in this sense. This might be useful so that commands
> could share code.

Not sure about methods decorated this way, where would you get an
instance to call them with? Unless they were class or static methods
of course.

Not that you need to be able to use methods for this approach to be
useful, just that I'm not sure it can include instance methods easily.

Cheers -Terry

Edward K. Ream

unread,
May 21, 2009, 12:10:45 PM5/21/09
to leo-e...@googlegroups.com

Many thanks for this work.  I'll play with it soon.

Edward

Edward K. Ream

unread,
Jun 1, 2009, 10:44:00 AM6/1/09
to leo-editor
On May 21, 11:10 am, "Edward K. Ream" <edream...@gmail.com> wrote:

> > I have now pushed g.command decorator. It pushes stuff to
> > g.app.global_commands_dict, which gets copied to c on commander
> > creation. it's rev 1889, you'll note that it's a simple
> > implementation. I didn't add g.button yet.
>
> Many thanks for this work.  I'll play with it soon.

I notice that executing the example script doesn't make the command
available to the present commander. It would be nice to fix that.

Edward

Ville M. Vainio

unread,
Jun 1, 2009, 11:58:24 AM6/1/09
to leo-e...@googlegroups.com
On Mon, Jun 1, 2009 at 5:44 PM, Edward K. Ream <edre...@gmail.com> wrote:

> I notice that executing the example script doesn't make the command
> available to the present commander.  It would be nice to fix that.

Yeah, that's in the cards.

Ville M. Vainio

unread,
Jun 2, 2009, 12:31:05 PM6/2/09
to leo-e...@googlegroups.com
On Mon, Jun 1, 2009 at 5:44 PM, Edward K. Ream <edre...@gmail.com> wrote:

> I notice that executing the example script doesn't make the command
> available to the present commander.  It would be nice to fix that.

Done in trunk.

I also added g.app.commanders() which returns a list of commanders (it
seemed to be missing).

I think @g.command is now he simplest way to create new commands:

- They magically appear on all new commanders
- They (almost as magically) appear on every commander that already exists

I'm open for thoughts & feedback for a certain period of time before
documenting this as the recommended way to create commands ;-)

Note that if you are into this "Object oriented programming" fad, you
can register your commands using closures, like this:

class MyCommands:
def create(self):

@g.command('foo1')
def foo1_f(event):
self.foo = 1

@g.command('foo2')
def foo1_f(event):
self.foo = 2

@g.command('foo-print')
def foo_print_f(event):
g.es('foo is', self.foo)


o = MyCommands()
o.create()

Edward K. Ream

unread,
Jun 2, 2009, 2:03:11 PM6/2/09
to leo-e...@googlegroups.com
On Tue, Jun 2, 2009 at 11:31 AM, Ville M. Vainio <viva...@gmail.com> wrote:

I think @g.command is now he simplest way to create new commands:

- They magically appear on all new commanders
- They (almost as magically) appear on every commander that already exists

Thanks for this work, Ville.  I'd like to use @g.command for all commands in the Leo 4.7 cycle.

Edward

Ville M. Vainio

unread,
Jun 3, 2009, 12:13:03 PM6/3/09
to leo-e...@googlegroups.com
On Tue, Jun 2, 2009 at 7:31 PM, Ville M. Vainio <viva...@gmail.com> wrote:

> I'm open for thoughts & feedback for a certain period of time before
> documenting this as the recommended way to create commands ;-)

Documented in LeoDocs, like this:

http://pastebin.com/m38241a28

(no need to reply with typofixes, noted some already myself)

Reply all
Reply to author
Forward
0 new messages