Re: Better @g.command decorator
On Mon, May 18, 2015 at 3:45 AM, reinhard.engel...@googlemail.com wrote: Maybe you should using different decorators. I think the present code is good enough. Certainly @keybinding isn't needed. The defaults belong in leoSettings.leo. Imo, the @g.cmd project has completed successfully. EKR -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
Maybe you should using different decorators. No, it doesn't work. In leo/commands/helpCommands I replaced: def cmd(name): '''Command decorator for the helpCommands class.''' return g.new_cmd_decorator(name,['c','helpCommands',]) with cmd = g.cmd where g.cmd is defined as you suggest, except it makes entries in g.global_commands_dict. I also added traces. After thinking a little about this problem: Maybe you are trying to pack too much into one decorator. My solution wasn't meant to work with a variable list of additional parameters (i.e. [ 'c','helpCommands',]), but only with a variable number of names (aka synonyms). I don't know how many commands are involved that need additional parameters (i.e. your key bindings). But instead of making case distinctions in your command dispatch function you could use two decorators: @keybinding('F12') @command('help-for-leo') defhelpForLeo(self): ... The '@keybinding' decorator could then access the global_command_table enhance the command. This would make your intentions explicit and simplify the command dispatch function. Reinhard -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
Forget the decorator! I'm not sure what you try to accomplish. Your publicCommands tables in Leo already associate command_names with functions or methods, so you don't need a decorator for that. As you told Terry (somewhere above) you need to retrieve the information if you need a 'self' argument or not (in other words: if you need to call a method or a function). But for that you only need a simple test. Here is a solution: # No-decorator solution def pureFunction(event=None): pureFunction docstring print(-- function:, event) class MyClass: def instanceMethod(self, event=None): instanceMethod docstring print(** method:, event) # You already have created these tables manually in Leo. # So you don't need a decorator to do that. publicCommands = { 'first-command': pureFunction, 'second-command': MyClass.instanceMethod, } # And so you only need to test # if you need to call a method or a function. # No need for a decorator either. def tests(): for command in publicCommands: func = publicCommands[command] print(command, func, str(func)) if '.' in str(func): print('Command %s is a method.' % command) # Calling your method func('self', 'anEvent') else: print('Command %s is a function.' % command) # Calling your function func(anEvent) tests() Decorators are not meant to be called as functions. They must appear directly above function/method (or class) that is to be enhanced or modified. They are not meant to modify functions/methods whose names are retrieved from elsewhere (i.e. tables). So if you wanted to apply decorators i.e. to your help-methods, you would put the decorator immediately before the function definition: @cmd('help-for-help') def helpForHelp(self, ...): return Some Help This way, you don't have to manually construct (and maintain!) separate command tables. If you still need such a dispatch table it could be created automatically by the decorator. Reinhard -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
On Sun, May 17, 2015 at 4:04 AM, reinhard.engel...@googlemail.com wrote: Forget the decorator! :-) The per-class (per-module) decorators in Leo's code do work, they are defined before the class to which they apply and the decorators themselves appear before what *looks *to be a method. We now both agree that no single g.cmd decorator can do the job, but defining per-class decorators in the various leo/commands/*.py files is more elegant than the old publicCommands tables. g.new_cmd_decorator and its helper, g.ivars2instance, make defining per-class cmd decorators easy. Edward P.S. As I write this, I see that decorators can never use g.cmd_instance_dict. However, this dict may have other uses, so it will be retained, along with its permanent unit test, g.check_cmd_instance_dict. EKR -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
There is no need to 'compute' the class, because the class is stored in the decorator, wenn the command is created. I post my code from the other thread here again. When Leo sees a command, it looks up the wrapper in the dict and simply calls the wrapper with an event arg. The *wrapper *must compute self if and only if the wrapper represents a wrapper of a method. Yes, this is complicated. The following is my best illustration of the problem that can't be solved. The code just knows the class. It is as easy as it can get: commands_dict = {} # Use as function/method decorator def cmd(*command_names): class Decorator: def __init__(self, func): self.func = func try:# Python3 self.isMethod = '.' in func.__qualname__ except AttributeError: # Python2 self.isMethod = 'instance' in str(func) for command_name in command_names: commands_dict[command_name] = self.__call__ def __call__(self, *args, **kwargs): if self.isMethod: return self.func(args[0], *args, **kwargs) else: return self.func(*args, **kwargs) return Decorator @cmd('command-one', 'first-command') def pureFunction(event=None): pureFunction docstring print(-- function:, event) class MyClass: @cmd('command-two', 'second-command', 'another-second-command') def instanceMethod(self, event=None): instanceMethod docstring print(** method:, event) def tests(): Calling functions/methods from the command_dict for command in commands_dict: commands_dict[command](I'm command %s. % command) tests() Reinhard -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
On Thursday, May 14, 2015 at 11:09:47 AM UTC-5, Edward K. Ream wrote: Not sure why it works, but I see that it does... this decorator should replace g.command and all other cmd decorators. Upon further review, I don't believe this problem is solvable. This snippet: type0 = args and str(type(args[0])) isClass = '.' in type0 if g.isPython3 else type0 == type 'instance' className = args[0].__class__.__name__ if isClass else None is a simplified version of how Reinhard proposed to calculate the class name. Alas, this code executes too late. Indeed, wrappers must be entered into a global commands_dict *when the decorator first executes*. There are no args available at that time. At present, g.new_cmd_decorator does this as follows: def new_cmd_decorator(name,ivars): ''' Return a new decorator for a command with the given name. Compute the class instance using the ivar string or list. ''' def _decorator(func): def wrapper(event): c = event.c self = g.ivars2instance(c,g,ivars) func(self,event=event) # Don't use a keyword for self. # This allows the VimCommands class to use vc instead. wrapper.__name__ = 'wrapper: %s' % name wrapper.__doc__ = func.__doc__ global_commands_dict[name]=wrapper # Put the *wrapper* into the global dict. return func # The decorator must return the func itself. return _decorator This works because ivars is bound by the per-command (actually per-module) decorators. When Leo sees a command, it looks up the wrapper in the dict and simply calls the wrapper with an event arg. The *wrapper *must compute self if and only if the wrapper represents a wrapper of a method. Yes, this is complicated. The following is my best illustration of the problem that can't be solved. from functools import wraps commands_dict = {} def cmd(name): def _decorator(func): # Compute ivar name *here* if 0: # This won't work. No args are available. type0 = args and str(type(args[0])) isClass = '.' in type0 if g.isPython3 else type0 == type 'instance' className = args[0].__class__.__name__ if isClass else None className = '???' if className: @wraps(func) def wrapper(event): c = event.get('c') ivars = g.cmd_instance_dict.get(className) if ivars: obj = ivars2instance(c,g,ivars) self = getattr(c,ivarName) func(self,event) else: g.trace('not a known class:',className) else: @wraps(func) def wrapper(event): func(event) commands_dict[name]=wrapper return func return _decorator @cmd('command1') def pureFunction(event=None): g.trace('event',event) class MyClass: @cmd('command2') def aMethod(self, event=None): g.trace('self',self.__class__.__name__,'event',event) # Simulate Leo's command dispatching. d = commands_dict event = {'c':c} for command in d.keys(): wrapper = d.get(command) wrapper(event) if 0: # These tests are irrelevant. # The calls must be made from commands_dict. pureFunction() pureFunction(abc) pureFunction(pureFunction) pureFunction(MyClass) MyClass().aMethod() MyClass().aMethod(abc) MyClass().aMethod(pureFunction) MyClass().aMethod(MyClass) In other words, I can see no way to compute className when needed. Once again, we seem to be stuck. Happily, the present decorators work just fine. Your comments, please. Edward -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
On Sat, May 16, 2015 at 4:59 PM, reinhard.engel...@googlemail.com wrote: There is no need to 'compute' the class, because the class is stored in the decorator, w hen the command is created. I post my code from the other thread here again. Yes, I think you have solved all the problems, including multiple synonyms for commands. Well done! I'll use your new code asap. Edward -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
On Saturday, May 16, 2015 at 6:26:17 PM UTC-5, Edward K. Ream wrote: I think you have solved all the problems... No, it doesn't work. In leo/commands/helpCommands I replaced: def cmd(name): '''Command decorator for the helpCommands class.''' return g.new_cmd_decorator(name,['c','helpCommands',]) with cmd = g.cmd where g.cmd is defined as you suggest, except it makes entries in g.global_commands_dict. I also added traces. # Use as function/method decorator def cmd(*command_names): class Decorator: def __init__(self, func): self.func = func g.trace(command_names,str(func)) try: # Python3 self.isMethod = '.' in func.__qualname__ except AttributeError: # Python2 self.isMethod = 'instance' in str(func) for command_name in command_names: global_commands_dict[command_name] = self.__call__ def __call__(self, *args, **kwargs): g.trace('func: %s\narg: %s\nkwargs: %s' % (self.func,args,kwargs )) if self.isMethod: return self.func(args[0], *args, **kwargs) else: return self.func(*args, **kwargs) return Decorator Here are the traces when Leo loads: ... reading settings in C:\leo.repo\leo-editor\leo\config\leoSettings.leo __init__ ('help',) function help at 0x02C4FF70 __init__ ('help-for-abbreviations',) function helpForAbbreviations at 0x02C53070 __init__ ('help-for-autocompletion',) function helpForAutocompletion at 0x02C53130 __init__ ('help-for-bindings',) function helpForBindings at 0x02C531F0 __init__ ('help-for-command',) function helpForCommand at 0x02C532B0 __init__ ('help-for-creating-external-files',) function helpForCreatingExternalFiles at 0x02C53430 __init__ ('help-for-debugging-commands',) function helpForDebuggingCommands at 0x02C53530 __init__ ('help-for-drag-and-drop',) function helpForDragAndDrop at 0x02C535B0 __init__ ('help-for-dynamic-abbreviations',) function helpForDynamicAbbreviations at 0x02C53670 __init__ ('help-for-find-commands',) function helpForFindCommands at 0x02C53730 __init__ ('help-for-minibuffer',) function helpForMinibuffer at 0x02C537F0 __init__ ('help-for-regular-expressions',) function helpForRegularExpressions at 0x02C538B0 __init__ ('help-for-scripting',) function helpForScripting at 0x02C53970 __init__ ('print-settings',) function printSettings at 0x02C53A30 __init__ ('help-for-python',) function pythonHelp at 0x02C53AF0 reading settings in c:\Users\edreamleo\.leo\myLeoSettings.leo reading settings in C:\Users\edreamleo\ekr.leo ... As you can see, everything is a function. Naturually, this causes a code crash when the init method executes. For example, F12 (python-help) gives: exception executing command Traceback (most recent call last): File c:\leo.repo\leo-editor\leo\core\leoCommands.py, line 6886, in doCommand val = command(event) File c:\leo.repo\leo-editor\leo\core\leoGlobals.py, line 1452, in __call__ return self.func(*args, **kwargs) File c:\leo.repo\leo-editor\leo\commands\helpCommands.py, line 1114, in pythonHelp k.getArg(event,tag,1,self.pythonHelp) AttributeError: LeoKeyEvent instance has no attribute 'pythonHelp' Maybe I'm misunderstanding something. Reinhard, the ball is in your court. Edward -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
On Thu, May 14, 2015 at 5:04 AM, reinhard.engel...@googlemail.com wrote: So it looks like we're stuck. Any ideas? Apart from using a sophisticated decorator you could just pass the class name as an optional parameter in @cmd(command_name, class_name=None) I'm no expert here, but this would require about 150 changes in the Leo code base and could be done by a regex search/replace. I'd rather stick to per-class decorators rather than add the class name. It's too ugly. This would be the *worst *kind of complexity: something that complicates all uses of the decorator. In contrast, your sophisticated decorator completely encapsulates all the complexity. This is the *best *kind of complexity: something that simplifies the uses of the decorator. Being able to use g.command everywhere will be a big win. Edward -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
On Thu, May 14, 2015 at 4:44 AM, reinhard.engel...@googlemail.com wrote: So it looks like we're stuck. Any ideas? This works for both Python2 and Python3: Excellent! Not sure why it works, but I see that it does :-) I'm in the midst of a big code reorg at present. When that is done, this decorator should replace g.command and all other cmd decorators. Many thanks for solving this puzzle. Edward -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
So it looks like we're stuck. Any ideas? This works for both Python2 and Python3: from functools import wraps isPython3 = True def cmd(command_name): def inner_cmd(func): @wraps(func) def _decorator(*args, **kwargs): type0 = args and str(type(args[0])) or if isPython3 and '.' in type0: className = args[0].__class__.__name__ elif not isPython3 and type0 == type 'instance': className = args[0].__class__.__name__ else: className = None print('** func:', func.__name__, 'class:', className) return func(*args, **kwargs) return _decorator return inner_cmd @cmd('command1') def pureFunction(event=None): print('-- pureFunction:', event) return (...exit pureFunction) class MyClass: @cmd('command2') def aMethod(self, event=None): print('-- self',self,'event',event) def tests(): pureFunction() pureFunction(abc) pureFunction(pureFunction) pureFunction(MyClass) MyClass().aMethod() MyClass().aMethod(abc) MyClass().aMethod(pureFunction) MyClass().aMethod(MyClass) tests() Reinhard -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
So it looks like we're stuck. Any ideas? Apart from using a sophisticated decorator you could just pass the class name as an optional parameter in @cmd(command_name, class_name=None) I'm no expert here, but this would require about 150 changes in the Leo code base and could be done by a regex search/replace. Of course, this solution would be less robust when a class name changes. But then, how often does this happen? Reinhard -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
It's even easier: You don't need a class decorator but can retrieve the class nam from th decorator's __dict__: commands_dict = {} ivars_dict = {} # Use as function/method decorator def cmd(command_name): class Decorator: def __init__(self, func): self.func = func callable = str(self.__dict__['func']).split()[1] print(callable:, callable) self.isMethod = '.' in callable if self.isMethod: self.className = callable.split('.')[0] else: self.className = None commands_dict[command_name] = self.__call__ ivars_dict[command_name] = 'testClass' def __call__(self, *args, **kwargs): if self.isMethod: print(*** className at runtime:, self.className) return self.func(args[0], *args, **kwargs) # args[0]: implicit 'self' of class method else: return self.func(*args, **kwargs) return Decorator @cmd('command1') def pureFunction(event=None): pureFunction docstring print(pureFunction with argument:, event) return (...exit pureFunction) class MyClass: @cmd('command2') def instanceMethod(self, event=None): instanceMethod docstring print(MyClass.instanceMethod with argument:, event) return (...exit instanceMethod) for command in commands_dict: func = commands_dict[command] result = func('anArgument') print(result:, result) Reinhard -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
Instead of using per plass decorators you might use a class decorator that enhances the function/method decorators with the class name: commands_dict = {} ivars_dict = {} # Use as function/method decorator def cmd(command_name): class Decorator: def __init__(self, func): self.func = func try:# for Python 2 self.isMethod = 'self' in func.func_code__.co_varnames except: # for Python 3 self.isMethod = 'self' in func.__code__.co_varnames commands_dict[command_name] = self.__call__ ivars_dict[command_name] = 'testClass' def __call__(self, *args, **kwargs): if self.isMethod: print(*** className at runtime call:, self.className) return self.func(args[0], *args, **kwargs) # args[0]: implicit 'self' of class method else: return self.func(*args, **kwargs) return Decorator # Use as class decorator def addClassnames(aClass): for key, value in aClass.__dict__.items(): isDecorator = 'Decorator' in str(value) if isDecorator: value.__dict__['className'] = aClass.__name__ def onCall(*args, **kwargs): return aClass(*args, **kwargs) return onCall @cmd('command1') def pureFunction(event=None): pureFunction docstring print(pureFunction with argument:, event) return (...exit pureFunction) @addClassnames class MyClass: @cmd('command2') def instanceMethod(self, event=None): instanceMethod docstring print(MyClass.instanceMethod with argument:, event) return (...exit instanceMethod) for command in commands_dict: func = commands_dict[command] result = func('anArgument') print(result:, result) Reinhard -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
On Wednesday, May 13, 2015 at 5:13:46 AM UTC-5, reinhard.engel...@googlemail.com wrote: It's even easier... There is no obvious way to recover the class name in Python 2. Here is my test code: def cmd(command_name): def _decorator(func): base = func.__code__ if g.isPython3 else func.func_code isMethod = 'self' in base.co_varnames g.trace('method: %5s %s' % (isMethod,func)) return _decorator @cmd('command1') def pureFunction(event=None): g.trace(event) return (...exit pureFunction) class MyClass: @cmd('command2') def aMethod(self, event=None): g.trace('self',self,'event',event) With Python 3 this produces _decorator method: False function pureFunction at 0x08A11A08 _decorator method: True function MyClass.aMethod at 0x08A11A50 With Python 2 it produces: _decorator method: False function pureFunction at 0x094C05F0 _decorator method: True function aMethod at 0x094C06F0 You would think there recovering the class name would be easy. But no. func.im_class does not exist because func is not a bound method. __qualname__ exists only in Python 3. A close examination of dir(func) shows no other obvious way to get the class name. So it looks like we're stuck. Any ideas? Edward -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
On Wed, May 13, 2015 at 5:13 AM, reinhard.engel...@googlemail.com wrote: It's even easier: You don't need a class decorator but can retrieve the class name from the decorator's __dict__. Many thanks for your continued work. Given the class name, the decorator could get the instance from g.cmd_instance_dict. This would eliminate the need for per-class decorators. This is excellent collaboration. I've been focused on other aspects of decorators, especially g.command. I'm glad you have pressed ahead. I'll try to fold what you have done into the existing framework. If it succeeds, we'll have a beautiful, general solution to an important problem. Edward -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
On Monday, May 11, 2015 at 6:51:38 AM UTC-5, john lunzer wrote: Out of curiosity, how do you ensure singleton-ness of the LeoApp class? I didn't see anything special precautions in the class definition. There is nothing special about the LeoApp class. It simply gets instantiated once. Some people consider singleton's to be bad design. I agree, if that means that being a singleton actually affects the class's code. In particular, a singleton class that uses static methods (without self) is asking for big trouble. That's why I am willing to go to so much trouble to have @cmd decorators be able to reconstitute the self argument. Methods that implement Leo commands must be first-class methods of their class, not self-less functions. Many commands use self! EKR -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
Out of curiousity, how do you ensure singleton-ness of the LeoApp class? I didn't see anything special precautions in the class definition. On Sunday, May 10, 2015 at 1:10:43 PM UTC-4, Edward K. Ream wrote: On Sunday, May 10, 2015 at 11:51:23 AM UTC-5, Edward K. Ream wrote: * *g.cmd_instance_dict..is Leo's Rosetta Stone! The implications keep getting more interesting. 1. This dict is independent of packaging! The classes mentioned in the dict may be defined in any module. 2. Classes containing commands need not be singletons. And a good thing too: Leo's only singleton class is the LeoApp class, g.app. 3. This dict can and should include *all *of Leo's classes. It should be named something like g.global_instance_dict. EKR -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
On Sunday, May 10, 2015 at 7:03:02 AM UTC-5, Edward K. Ream wrote: The primary task is to associate a decorator with an *instance *of a class. The code that I pushed yesterday does this by defining a cmd decorator for each class that defines a Leo command. When I awoke this morning I saw that a single dict, say *g.cmd_instance_dict*, could eliminate the need for per-class decorators. Alas, there appears to be no way for the wrapper to recover the class name. Indeed, the func argument to the wrapper is a *function*, not a method. For example, func.__class__ is None. Following Reinhard's suggestions, it might be possible to use the inspect module or the func's code object to recover the enclosing class's name. But I wouldn't bet on it. So it appears that per-class cmd decorators will be required. It's not the end of the world :-) Edward -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
| On Mon, 11 May 2015 05:16:24 -0700 (PDT) | Edward K. Ream edream...@gmail.com wrote: :: On Sunday, May 10, 2015 at 7:03:02 AM UTC-5, Edward K. Ream wrote: The primary task is to associate a decorator with an *instance *of a class. The code that I pushed yesterday does this by defining a cmd decorator for each class that defines a Leo command. When I awoke this morning I saw that a single dict, say *g.cmd_instance_dict*, could eliminate the need for per-class decorators. Alas, there appears to be no way for the wrapper to recover the class name. Indeed, the func argument to the wrapper is a *function*, not a method. For example, func.__class__ is None. Following Reinhard's suggestions, it might be possible to use the inspect module or the func's code object to recover the enclosing class's name. But I wouldn't bet on it. So it appears that per-class cmd decorators will be required. It's not the end of the world :-) Right, that's a week from Wednesday :-) But are you suggesting different names for different decorators? That seems cumbersome. Have to admit I've lost track of which problem you're trying to solve, but: .. code:: py def dec(f): def x(*args, **kwargs): print args[0] f(*args, **kwargs) return x class test: @dec def atest(self, etc): print etc. t = test() t.atest(3) can get the class from args[0] - but I haven't been following closely enough to know if that's relevant. Cheers -Terry -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
On Sat, May 9, 2015 at 11:58 PM, reinhard.engel...@googlemail.com wrote: The main point is to tell a function from a method. If you don't want to use __qualname__ one might use the inspect module. But assuming the Python convention to name the first argument of method 'self', the foll o wing works for both Python 2 and 3: Thanks for all your suggestions. They have been helpful. The VimCommands class uses 'vc' instead of self. That isn't going to change. Decorators must adapt to existing code, not vice versa. The primary task is to associate a decorator with an *instance *of a class. The code that I pushed yesterday does this by defining a cmd decorator for each class that defines a Leo command. When I awoke this morning I saw that a single dict, say g.instance_dict, could eliminate the need for per-class decorators. A single *g.cmd* decorator could recover desired instance using this dict. Keys would be class names, values would be lists of attributes. For example, *cmd_instance_dict* = { 'AutoCompleter': ['c','k','autoCompleter'], 'LeoApp': ['g','app'], ... } This dict encapsulates the variable parts of the present cmd decorators. *Important*: the event sent to the *wrapper* contains the proper commander, so the wrapper can get the desired instance using g.ivars2instance. Two kinds of checks will be performed: 1. The g.cmd decorator itself will check that func.__class__.__name__ is in g.cmd_instance_dict.keys(). This is an import-time check. 2. Code in c.finishCreate will check that all the attributes in each list exist. g.ivars2instance already performs this check. This is a permanent unit test that can't be run with per-class definitions of cmd. *Note*: g.cmd_instance_dict and g.global_commands_dict must be in leoGlobals.py. They can't be in the LeoApp class because this class is not fully created when decorators execute. A final task in the new_dispatch project has nothing to do with decorators. A single kind of event must be sent to all commands. At present, some events do not have 'stroke' attributes, etc. Unifying events is likely to be tricky. Edward -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
On Sunday, May 10, 2015 at 7:03:02 AM UTC-5, Edward K. Ream wrote: single dict, say *g.cmd_instance_dict*, could eliminate the need for per-class decorators. This dict is Leo's Rosetta Stone! Here it is: cmd_instance_dict = { # Keys are class names, values are attribute chains. # Created from c.initObjects... 'AtFile': ['c','atFileCommands'], 'AutoCompleterClass': ['c','k','autoCompleter'], 'ChapterController': ['c','chapterController'], 'Commands': ['c'], 'ControlCommandsClass': ['c','controlCommands'], 'FileCommands': ['c','fileCommands'], 'KeyHandlerClass': ['c','k'], 'LeoApp': ['g','app'], 'LeoFind': ['c','findCommands'], 'LeoImportCommands': ['c','importCommands'], 'PrintingController': ['c','printingController'], 'RstCommands': ['c','rstCommands'], 'VimCommands': ['c','vimCommands'], 'Undoer': ['c','undoer'], # Created from and EditCommandsManager.__init__... 'AbbrevCommandsClass': ['c','abbrevCommands'], 'BufferCommandsClass': ['c','bufferCommands'], 'EditCommandsClass': ['c','editCommands'], 'ChapterCommandsClass': ['c','chapterCommands'], 'ControlCommandsClass': ['c','controlCommands'], 'DebugCommandsClass': ['c','debugCommands'], 'EditFileCommandsClass': ['c','editFileCommands'], 'HelpCommandsClass': ['c','helpCommands'], 'KeyHandlerCommandsClass': ['c','keyHandlerCommands'], 'KillBufferCommandsClass': ['c','killBufferCommands'], 'MacroCommandsClass': ['c','macroCommands'], 'RectangleCommandsClass': ['c','rectangleCommands'], 'RegisterCommandsClass': ['c','registerCommands'], 'SearchCommandsClass': ['c','searchCommands'], 'SpellCommandsClass': ['c','spellCommands'], } The dict contains entries for all classes that could conceivably define Leo commands. Not all classes should be in the dict. This is an important development. A variant of this dict, containing more classes, could be used in type analysis. Here is the code in leoGlobals.py that checks this dict: def check_cmd_instance_dict(c,g): ''' Check g.check_cmd_instance_dict. This is a permanent unit test, called from c.finishCreate. ''' d = cmd_instance_dict for key in d.keys(): ivars = d.get(key) obj = ivars2instance(c,g,ivars) if obj: name = obj.__class__.__name__ if name != key: g.trace('class mismatch',key,name) The check now passes. It is unlikely ever to fail again. The next steps will be: - Use this dict in the wrapper inside g.cmd. - Replace @cmd with @g.cmd everywhere. - Remove/disable all the per-class cmd decorators defined yesterday. - Unify all events passed to commands. *Summary* g.cmd_instance_dict is a wholly unexpected breakthrough: - It succinctly summarizes all of Leo's classes. - It can easily be checked for accuracy. - It allows the all important self argument (whatever its spelling) to be passed to methods. - Unless I am greatly mistaken, it will eliminate the need for per-class decorators. There is *no way* that Python could recreate the data in this dict. A big step forward. EKR -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
On Sunday, May 10, 2015 at 11:51:23 AM UTC-5, Edward K. Ream wrote: * *g.cmd_instance_dict..is Leo's Rosetta Stone! The implications keep getting more interesting. 1. This dict is independent of packaging! The classes mentioned in the dict may be defined in any module. 2. Classes containing commands need not be singletons. And a good thing too: Leo's only singleton class is the LeoApp class, g.app. 3. This dict can and should include *all *of Leo's classes. It should be named something like g.global_instance_dict. EKR -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
This works without static/class methods and is a little less involved: A nice idea, but __qualname__ is Python 3 only. The main point is to tell a function from a method. If you don't want to use __qualname__ one might use the inspect module. But assuming the Python convention to name the first argument of method 'self', the follwing works for both Python 2 and 3: try:# for Python 2 self.isMethod = 'self' in func.func_code__.co_varnames except: # for Python 3 self.isMethod = 'self' in func.__code__.co_varnames I used the try...except construct, so the code is not dependent on some global constant (ie. IS_PYTHON3). Any better solution to make this distictintion is welcome. Reinhard -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
I would *much *rather do this than use class methods anywhere in Leo. YMMV, but you have little chance of changing my mind. Yes, there is a bit more code involved, but it is completely encapsulated. In contrast, static/class methods have global consequences. I'm not going there. This works without static/class methods and is a little less involved: commands_dict = {} ivars_dict = {} def cmd(command_name): class Decorator: def __init__(self, func): self.func = func self.isMethod = '.' in func.__qualname__ commands_dict[command_name] = self.__call__ ivars_dict[command_name] = 'testClass' def __call__(self, *args, **kwargs): if self.isMethod: return self.func(args[0], *args, **kwargs) # args[0]: implicit 'self' of class method else: return self.func(*args, **kwargs) return Decorator @cmd('command1') def pureFunction(event=None): pureFunction docstring print(pureFunction with argument:, event) return (...exit pureFunction) class MyClass: @cmd('command2') def instanceMethod(self, event=None): instanceMethod docstring print(MyClass.instanceMethod with argument:, event) return (...exit instanceMethod) for command in commands_dict: func = commands_dict[command] result = func('anArgument') print(result:, result) Reinhard -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
*A two-level solution* Rather than using a single command-decorator, we must use separate decorators for each defining class: commands_dict = {} class TestClass: def cmd(name): def _decorator(func): commands_dict[name]= func ivars_dict[name] = 'testClass' # c.testClass is a singleton instance of TestClass. return func return _decorator @cmd('command1') def method1(self,event=None): '''method1 docstring.''' return self.__class__.__name__ The idea is that the dispatch code will look something like this: func = commands_dict.get(command_name) ivar = ivars_dict.get(command_name) obj = getattr(c,ivar) if ivar else c func(obj,event) It should now be pretty clear that there is no possible way for any decorator to know about c.testClass automatically. This knowledge must be built in to each decorator. What I don't understand: Why do you need instance methods (using 'self') if you are working with a singleton, because in this case: c.testClass == TestClass? Then you colud use static class methods; and with these *one *decorator works perfectly well: commands_dict = {} ivars_dict = {} def cmd(name): def _decorator(func): commands_dict[name] = func ivars_dict[name] = 'testClass' # c.testClass is a singleton instance of TestClass. return func return _decorator @cmd('command1') def pureFunction(event=None): pureFunction docstring print(pureFunction) class MyClass: @cmd('command2') def staticClassMethod(event=None): # No 'self' here! staticClassMethod docstring print(staticClassMethod) f1 = commands_dict['command1'] f1() print(ivars_dict['command1']) f2 = commands_dict['command2'] f2() print(ivars_dict['command2']) This gives the output: pureFunction testClass staticClassMethod testClass Of course, this doesn't work with instances of classes. But without looking at the Leo code, do you really want to bind Leo commands to instances of classes? Reinhard -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
On Tuesday, May 5, 2015 at 10:20:20 AM UTC-5, Edward K. Ream wrote: [The new @cmd] decorator would eliminate the need for command-definition tables that occur in various places in leoEditCommands.py and leoCommands.py. Rev 1789ef6 contains @button create decorators in scripts.leo that will soon automatically create decorators for all but 11 of the 447 commands defined in leoPy.leo. It was fun creating this script. It handles what otherwise be very tedious operations. The 11 remaining cases truly are special, so there is no need to refine the @button node further except to actually have it change Leo's source code. That will be coming a bit later... Note that this script must run in leoPy.leo a node called Found: getPublicCommands must also exist in leoPy.leo. This node is the result of the cfa-code command. EKR -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
In the 5th edition of 'Learning Python', pages 1289ff, Mark Lutz discusses extensively solutions for decorators that work on *both *simple functions and class-level methods (with passing instances!). Reinhard -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
On Fri, May 8, 2015 at 2:45 PM, reinhard.engel...@googlemail.com wrote: Why do you need instance methods (using 'self') ...you could use static class methods; and with these *one *decorator works perfectly well: That would work. But I won't do it. I have never seen a class methods that wasn't an invitation to trouble. They are never needed, and they instantly cause packaging difficulties. Instead, rev a533869 contains the following: 1. helpCommands.cmd: def cmd(name): '''Command decorator for the helpCommands class.''' return g.new_decorator(name,'helpCommands') 2. A common helper for all @cmd decorators: def new_decorator(name,ivars=None): ''' Return a new decorator for a command with the given name. Compute the class instance using the ivar string or list. ''' def _decorator(func): if new_dispatch: def wrapper(event): c = event.get('c') self = g.ivars2instance(c,ivars) event = event.get('mb_event') ### To be removed. # g.trace('self',self,'event',event,'func',func) func(self=self,event=event) wrapper.__name__ = 'wrapper-for-%s' % name wrapper.__doc__ = func.__doc__ g.app.global_commands_dict[name]=wrapper # Put the *wrapper* into the global dict. return func # The decorator must return the func itself. return _decorator 3. A helper that recovers the instance object: def ivars2instance(c,ivars): ''' Return the instance of c given by ivars. ivars may be empty, a string, or a list of strings. A special case: ivars may be 'g', indicating the leoGlobals module. ''' if not ivars: return c elif isString(ivars): return g if ivars == 'g' else getattr(c,ivars) else: obj = c for ivar in ivars: obj = getattr(obj,ivar) if not obj: g.trace('can not happen',ivars) return c return obj I would *much *rather do this than use class methods anywhere in Leo. YMMV, but you have little chance of changing my mind. Yes, there is a bit more code involved, but it is completely encapsulated. In contrast, static/class methods have global consequences. I'm not going there. Edward -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
On Wednesday, May 6, 2015 at 10:55:31 AM UTC-5, Edward K. Ream wrote: The goal is simple: improve the g.command decorator so that all commands can be defined like this: @g.command('command-name') def do_command(self,event=None): '''Implement the command''' This is turning into a real can of worms. The problems arise not so much from decorators as from the way-too-complex code that dispatches commands. Simplifying this mess now has top priority. c.universalCallback looks like it must go. I did not write this code, but I'll make sure that it's goals are accomplished in other ways. To be fair, there are many other culprits besides c.universalCallback and I wrote all of them :-) As usual, new code will be enabled by a global switch: g.new_dispatch switch. The old code will remain unchanged when this switch is False. This will allow the master branch to remain stable even while performing experiments. EKR -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.
Re: Better @g.command decorator
On Tuesday, May 5, 2015 at 10:20:20 AM UTC-5, Edward K. Ream wrote: The goal is simple: improve the g.command decorator so that all commands can be defined like this: @g.command('command-name') def do_command(self,event=None): '''Implement the command''' This is an interesting problem and surprisingly complex. The rest of this post is Engineering Notebook entry. Feel free to ignore. *Executive summary* - Simple decorators suffice. More complex decorators can do no more. - Decorators must enter *functions *into the commands dicts. - Leo's present command tables enter *bound methods* into the commands dicts. - Consequently, Leo's command-dispatching code must change. *The simplest cmd decorator* The following decorator is both the simplest thing that could possibly work and almost the only thing that could possibly work, but at we will see it will be slightly modified later: def cmd(name): def _decorator(func): commands_dict[name]=func return func return _decorator Lots of important details lie hidden in this code. To illustrate the complications, here is the testing code I am using: commands_dict = {} def cmd(name): def _decorator(func): commands_dict[name]=func return func return _decorator class TestClass: @cmd('command1') def method1(self,event=None): '''method1 docstring.''' return self.__class__.__name__ @cmd('command2')def func1(self,event=None): '''func1 docstring.''' return self.__class__.__name__ test = TestClass() for command,aClass in (('command1',test),('command2',c)): func = commands_dict.get(command) print('name: %s, docstring: %s class: %s' % ( func.__name__,func.__doc__,func.__class__.__name__)) # Simulate the action of c.doCommand. print('result: '+func(aClass,None)) You can run this script from any Leo body pane. Notes: 1. The script makes entries in commands_dict so as not to interfere with Leo's existing code. The production decorator will probably make entries in g.app.global_commands_dict. 2. The following script reports that cmd, method1 and method2 are functions, not methods: commands_dict = {} class aClass: def cmd(name): def _decorator(func): commands_dict[name]=func return func return _decorator @cmd('command1') def method1(self): pass def method2(self): pass g.trace(cmd,method1,method2) This means that there is *no way* to bind self automagically in the _decorator function. This conclusion was quite a surprise. In retrospect it makes sense. The decorator runs before any instance of the class exists. *Dispatching commands* Decorated methods can usually be used normally. Decorators would be useless if that were not so. However, in this case using decorated methods is far from easy. At present, the entries in Leo's commands dict are bound methods, so the code that dispatches commands need not know to which class such methods are bound. Alas, decorators define table whose entries must be functions, which are essentially unbound methods. In that case, Leo's command dispatcher has the following unpleasant choices: 1. Pass a standard value, say c, in place of the 'self' argument. This would be very confusing. 2. Use c instead of self in the signature of decorated method. Clearer, but pylint will complain that the method's first argument is not self. 3. Actually pass the proper instance to the decorated method. This leads us to... *A two-level solution* Rather than using a single command-decorator, we must use separate decorators for each defining class: commands_dict = {} class TestClass: def cmd(name): def _decorator(func): commands_dict[name]= func ivars_dict[name] = 'testClass' # c.testClass is a singleton instance of TestClass. return func return _decorator @cmd('command1') def method1(self,event=None): '''method1 docstring.''' return self.__class__.__name__ The idea is that the dispatch code will look something like this: func = commands_dict.get(command_name) ivar = ivars_dict.get(command_name) obj = getattr(c,ivar) if ivar else c func(obj,event) It should now be pretty clear that there is no possible way for any decorator to know about c.testClass automatically. This knowledge must be built in to each decorator. *Summary* Simple decorators suffice. Complex decorators can do no more. Leo-specific knowledge will be built into class-specific decorators. Leo's command-dispatching code must change to handle commands defined by decorators. A new dictionary will likely associate command names with subcommanders. The new scheme will be tested on just a few commands from various classes. Once that works, we can eliminate all command tables and all getPublicCommands methods. This will
Re: Better @g.command decorator
On Tuesday, May 5, 2015 at 10:20:20 AM UTC-5, Edward K. Ream wrote: The goal is simple: improve the g.command decorator so that all commands can be defined [using it] I'll define @g.cmd for development. When everything works I'll change @g.cmd to @g.command. Much safer than messing with @g.command directly. In effect, @g.cmd creates a lightweight branch. EKR -- You received this message because you are subscribed to the Google Groups leo-editor group. To unsubscribe from this group and stop receiving emails from it, send an email to leo-editor+unsubscr...@googlegroups.com. To post to this group, send email to leo-editor@googlegroups.com. Visit this group at http://groups.google.com/group/leo-editor. For more options, visit https://groups.google.com/d/optout.