diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4f4fdd167b2d105b52f860fdec88de10baa954a4..3cf2f97327ea1ce0e8a7dd40bd63d8116361d6cd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -106,6 +106,7 @@ INSTALL(TARGETS ${PYTHON_MODULE} SET (PYTHON_SOURCES dynamic_graph/__init__.py + dynamic_graph/attrpath.py dynamic_graph/entity.py dynamic_graph/signal_base.py dynamic_graph/matlab.py diff --git a/src/dynamic_graph/attrpath.py b/src/dynamic_graph/attrpath.py new file mode 100644 index 0000000000000000000000000000000000000000..d10778d44d6d1f94ee61125e763c571ff1162399 --- /dev/null +++ b/src/dynamic_graph/attrpath.py @@ -0,0 +1,110 @@ +# This module define the three functions: +# - getattrpath +# - setattrpath +# - existattrpath +# that work similarly as the get/setattr, but given path ("a.b.c.d") +# in input. Consider the following example: +# >>> setattrpath( e.__class__,"a.b.c.d",fun) +# with fun a function (self,*arg). Then, it is next possible to launch +# >>> e.a.b.c.d( ...) +# as if it was a classical member function of e. + + +class CommandPath(object): + """ + This class is only defined to implement a path of attribute + to store entity commands. It has no members except those automatically + defined at run time (which should be CommandPath or functions). + """ + mother=None + def __getattr__(self,name): + privateName=name+'_obj' + if privateName in self.__dict__: + obj=getattr(self,privateName) + obj.mother=self.mother + return obj + return object.__getattr__(self,name) + +def createCommandModule( target,name ): + def createGetter( name ): + def __( self ): + obj = getattr(self,name) + obj.mother=self + return obj + return __ + + privateName = name+'_obj' + setattr( target,privateName, CommandPath() ) + module = getattr( target,privateName ) + + if not isinstance(target,CommandPath) : + setattr( target,name, property(createGetter(privateName)) ) + +class CommandLauncher(object): + """ + """ + mother=None + fun=None + def __init__(self,fun): self.fun=fun + def __call__(self,*arg): + return self.fun(self.mother,*arg) + +def createCommandLauncher( target,name,fun ): + if isinstance(target,CommandPath) : + privateName = name+'_obj' + setattr( target,privateName, CommandLauncher(fun) ) + else: + setattr( target,name,fun ) + + +def setattrpath( target,path,attribute ): + """ + Create in target an new attribute with value path (available at + target.path1. ... .pathn). + """ + pathk=target + read=True + if type(path)==type(str()): path=path.split('.') + for tokenk in path[0:-1]: + if (not read) | (tokenk not in pathk.__dict__): + read=False + createCommandModule(pathk,tokenk) + pathk = getattr(pathk,tokenk+"_obj") + if callable(attribute): + createCommandLauncher( pathk,path[-1],attribute ) + else: + print "Should not happen" + setattr(pathk,path[-1],attribute ) + +def getattrpath( target,path ): + """ + Get in target the value located at path (available at + target.path1. ... .pathn). + """ + pathk=target + if type(path)==type(str()): path=path.split('.') + for tokenk in path: + privateName=tokenk+"_obj" + if (privateName in pathk.__dict__): pathk = getattr(pathk,privateName) + else: + if ( tokenk in pathk.__dict__): pathk = getattr(pathk,tokenk) + else: + raise Exception('Path does not exist -- while accessing "'+tokenk+'" in '+'.'.join(path)) + return pathk + +def existattrpath( target,path ): + """ + Check for the existence in target of a value located at path (available at + target.path1. ... .pathn). + """ + pathk=target + if type(path)==type(str()): path=path.split('.') + for tokenk in path[0:-1]: + print 'check ',tokenk + privateName=tokenk+"_obj" + if (privateName not in pathk.__dict__): + return False + pathk = getattr(pathk,privateName) + name=path[-1] + privateName=name+"_obj" + return (name in pathk.__dict__)|(privateName in pathk.__dict__) diff --git a/src/dynamic_graph/entity.py b/src/dynamic_graph/entity.py index 7019ad152f7c0cec23e97c80666c568c9e3dc477..6c8e828d7885b5b3c1528b69af920fb4bc4ee9ea 100644 --- a/src/dynamic_graph/entity.py +++ b/src/dynamic_graph/entity.py @@ -1,37 +1,18 @@ """ Copyright (C) 2010 CNRS - Author: Florent Lamiraux + Author: Florent Lamiraux, Nicolas Mansard """ import wrap, signal_base, new +from attrpath import setattrpath -entityClassNameList = [] if 'display' not in globals().keys(): def display(s): print(s) - -def commandMethod(name, docstring) : - def method(self, *arg): - return wrap.entity_execute_command(self.obj, name, arg) - method.__doc__ = docstring - return method - -def initEntity(self, name): - """ - Common constructor of Entity classes - """ - Entity.__init__(self, self.className, name) - if not self.__class__.commandCreated: - # Get list of commands of the Entity object - commands = wrap.entity_list_commands(self.obj) - # for each command, add a method with the name of the command - for command in commands: - docstring = wrap.entity_get_command_docstring(self.obj, command) - setattr(self.__class__, command, commandMethod(command, docstring)) - self.__class__.commandCreated = True - - +# --- FACTORY ------------------------------------------------------------------ +# --- FACTORY ------------------------------------------------------------------ +# --- FACTORY ------------------------------------------------------------------ class PyEntityFactoryClass(type): """ @@ -41,7 +22,7 @@ class PyEntityFactoryClass(type): def __new__(factory, className ): EntityClass = type.__new__(factory, className, (Entity,), {}) EntityClass.className = className - EntityClass.__init__ = initEntity + EntityClass.__init__ = Entity.initEntity EntityClass.commandCreated = False return EntityClass @@ -56,16 +37,20 @@ def PyEntityFactory( className, context ): def updateEntityClasses(dictionary): """ - For all c++entity types that are not in the pyentity class list (entityClassNameList) - run the factory and store the new type in the given context (dictionary). + For all c++entity types that are not in the pyentity class list + (entityClassNameList) run the factory and store the new type in the given + context (dictionary). """ cxx_entityList = wrap.factory_get_entity_class_list() - for e in filter(lambda x: not x in entityClassNameList, cxx_entityList): + for e in filter(lambda x: not x in Entity.entityClassNameList, cxx_entityList): # Store new class in dictionary with class name PyEntityFactory( e,dictionary ) # Store class name in local list - entityClassNameList.append(e) + Entity.entityClassNameList.append(e) +# --- ENTITY ------------------------------------------------------------------- +# --- ENTITY ------------------------------------------------------------------- +# --- ENTITY ------------------------------------------------------------------- class Entity (object) : """ @@ -81,6 +66,18 @@ class Entity (object) : """ object.__setattr__(self, 'obj', wrap.create_entity(className, instanceName) ) + @staticmethod + def initEntity(self, name): + """ + Common constructor of specialized Entity classes. This function is bound + by the factory to each new class derivated from the Entity class as the + constructor of the new class. + """ + Entity.__init__(self, self.className, name) + if not self.__class__.commandCreated: + self.boundClassCommands() + self.__class__.commandCreated = True + @property def name(self) : return wrap.entity_get_name(self.obj) @@ -148,6 +145,39 @@ class Entity (object) : except: object.__getattr__(self, name) + def __setattr__(self, name, value): + if name in map(lambda s: s.getName().split(':')[-1],self.signals()): + raise NameError(name+" already designates a signal. " + "It is not advised to set a new attribute of the same name.") + object.__setattr__(self, name, value) + + # --- COMMANDS BINDER ----------------------------------------------------- + # List of all the entity classes from the c++ factory, that have been bound + # bind the py factory. + entityClassNameList = [] + + # This function dynamically create the function object that runs the command. + @staticmethod + def createCommandBind(name, docstring) : + def commandBind(self, *arg): + return wrap.entity_execute_command(self.obj, name, arg) + commandBind.__doc__ = docstring + return commandBind + + def boundClassCommands(self): + """ + This static function has to be called from a class heritating from Entity. + It should be called only once. It parses the list of commands obtained from + c++, and bind each of them to a python class method. + """ + # Get list of commands of the Entity object + commands = wrap.entity_list_commands(self.obj) + # for each command, add a method with the name of the command + for cmdstr in commands: + docstr = wrap.entity_get_command_docstring(self.obj, cmdstr) + cmdpy = Entity.createCommandBind(cmdstr, docstr) + setattrpath(self.__class__, cmdstr, cmdpy) + def boundNewCommand(self,cmdName): """ At construction, all existing commands are bound directly in the class. @@ -157,7 +187,8 @@ class Entity (object) : if (cmdName in self.__dict__) | (cmdName in self.__class__.__dict__): print "Warning: command ",cmdName," will overwrite an object attribute." docstring = wrap.entity_get_command_docstring(self.obj, cmdName) - cmd = commandMethod(cmdName,docstring) + cmd = Entity.createCommandBind(cmdName,docstring) + # Limitation (todo): does not handle for path attribute name (see setattrpath). setattr(self,cmdName,new.instancemethod( cmd, self,self.__class__)) def boundAllNewCommands(self): @@ -170,9 +201,3 @@ class Entity (object) : cmdList = filter(lambda x: not x in self.__class__.__dict__, cmdList) for cmd in cmdList: self.boundNewCommand( cmd ) - - # Script short-cuts: don't use this syntaxt in python coding, - # use it for debuging online only! - @property - def sigs(self): - self.displaySignals() diff --git a/src/dynamic_graph/script_shortcuts.py b/src/dynamic_graph/script_shortcuts.py index cd6648ab52a6dddb0c173daa05f7f081666154d9..e7ca93172f28de99aad6bc142de4e4b11f3c38aa 100644 --- a/src/dynamic_graph/script_shortcuts.py +++ b/src/dynamic_graph/script_shortcuts.py @@ -77,9 +77,20 @@ class SignalDepPrint: setattr(SignalBase,'deps',property(SignalDepPrint)) +setattr(Entity,'sigs',property(Entity.displaySignals)) setattr(Entity,'__repr__',Entity.__str__) # Changing prompt import sys sys.ps1 = '% ' +# Enable function that can be call without()def optionalparentheses(f): +def optionalparentheses(f): + class decoclass: + def __repr__(self): + res=f() + if isinstance(res,str): return res + else: return '' + def __call__(self,*arg): + return f(*arg) + return decoclass() diff --git a/src/dynamic_graph/signal_base.py b/src/dynamic_graph/signal_base.py index fade3a9b5ecbb72dc33c3091af813ce94557f477..dc3e144ded6ce70b8355cf112847ec587b77d40c 100644 --- a/src/dynamic_graph/signal_base.py +++ b/src/dynamic_graph/signal_base.py @@ -23,7 +23,7 @@ def stringToTuple (vector) : # check size if len(vector) != size: raise TypeError('displayed size ' + - size + ' of vector does not fit actual size: ' + str(size) + ' of vector does not fit actual size: ' + str(len(vector))) res = map(float, vector) return tuple (res) @@ -238,14 +238,4 @@ class SignalBase (object) : """ return(wrap.signal_base_display_dependencies(self.obj,iter)) - # Script short-cuts: don't use this syntaxt in python coding, - # use it for debuging online only! - @property - def m(self): - """ - m stands for matlab: display the content of the - signal with matlab style, for debuging and transfert. - matlab-style to be coded ... - """ - print(self.value)