Lists as members are not unique?



  • On 21/03/2013 at 16:51, xxxxxxxx wrote:

    During the process of porting a C++ plugin to Python I stumbled upon something REALLY wacky.
    In C++ we can write a plugin that consists of a Gedialog plugin and a TagData plugin.
    Then we can get the Tag plugin's class member variables from within the GeDialog code and use it.

    Upon doing this same thing in Python. It seemed to mostly work the same way as C++...Except in the case of lists (arrays) for some strange reason.
    If I add a list as a class member of the tag. Then change that list via the GeDialog plugin. When I make a new tag on another object. That list's values get carried over to that new tag!!?
    In other words. The class member list in my tags are not unique ( per tag ) as they are supposed to be.

    Other types of variables don't do this... Only lists.
    Every time a new tag is created. Other types of class members are unique as expected. But not when it comes to lists.

    Here's an example of a GeDialog plugin that sets and gets the class members on a tag plugin.

    import c4d,sys,os  
    from c4d import plugins, utils, bitmaps, gui, documents  
      
    TAG_PLUGIN_ID = 1000008           # THIS id IS FOR TESTING PUPOSES ONLY!!!  
    DIALOG_PLUGIN_ID =1000009         # THIS id IS FOR TESTING PUPOSES ONLY!!!   
      
      
    ################################################################  
    ###################### Tag Plugin ##############################  
      
    class StorageTag(plugins.TagData) :  
      
      tagListData = ["Nothing"]            #A list that will be changed by the dialog plugin  
      tagVariableValue = "Nothing"        #A variable that will be changed by the dialog plugin  
      
      def Init(self, tag) :  
          data = tag.GetDataInstance()      
          return True  
       
      def Execute(self, tag, doc, op, bt, priority, flags) :       
          return True     
      
    if __name__ == "__main__":  
     path, fn = os.path.split(__file__)  
     bmp = bitmaps.BaseBitmap()  
     bmp.InitWith(os.path.join(path, "res/icons/", "myicon.png"))     
     plugins.RegisterTagPlugin(TAG_PLUGIN_ID, "PyStorageTag",c4d.TAG_VISIBLE + c4d.TAG_EXPRESSION,StorageTag,"MyTag_dec", bmp)  
       
      
    ################################################################  
    ################### Dialog Plugin ##############################  
      
      
    #enums  
    BUTTON =  1001  
    BUTTON2=  1002  
      
    class MyDialog_Gui(gui.GeDialog) :  
       
      def CreateLayout(self) :    
          self.SetTitle("Dialog")       
          self.AddButton(BUTTON, c4d.BFH_CENTER, 60, 10, name="Press Me")  
          self.AddButton(BUTTON2, c4d.BFH_CENTER, 80, 10, name="Get Tag value")  
          return True  
        
      def InitValues(self) :   
          return True  
      
      def Command(self, id, msg) :  
        
          doc = documents.GetActiveDocument()  
      
          activeObj = doc.GetActiveObject()                     #The obect holding the tag  
          if not activeObj: return False  
          tag = activeObj.GetTag(TAG_PLUGIN_ID)                 #Get the tag on the object  
          if not tag: return False  
      
          st = tag.GetNodeData()                                #Get The tag's data (including the class members)              
      
          if id == BUTTON:              
              st.tagVariableValue    = "variable Value Changed"    #Change the value of the tag's member variable          
              st.tagListData[0] = "List Value Changed"          #Change the value of the tag's member list          
                    
          if id == BUTTON2:  
              print st.tagVariableValue                         #Get the current values of the tag's members  
              print st.tagListData[0]              
      
          c4d.EventAdd()      
          return True  
      
        
    class myDialog_Main(plugins.CommandData) :  
      dialog = None  
        
      def Execute(self, doc) :  
          if self.dialog is None:  
              self.dialog = MyDialog_Gui()  
          return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=DIALOG_PLUGIN_ID, defaultw=0, defaulth=0, xpos=-1, ypos=-1)  
            
      def RestoreLayout(self, sec_ref) :  
          if self.dialog is None:  
              self.dialog = MyDialog_Gui()  
          return self.dialog.Restore(pluginid=DIALOG_PLUGIN_ID, secret=sec_ref)   
      
    if __name__ == "__main__":  
     path, fn = os.path.split(__file__)  
     bmp = bitmaps.BaseBitmap()  
     bmp.InitWith(os.path.join(path, "res/icons/", "None"))  
     plugins.RegisterCommandPlugin(DIALOG_PLUGIN_ID, "My Dialog",0,None,"", myDialog_Main())
    

    This is really wacky.  And doesn't seem to happen in C++ plugins.
    How do I stop lists from spilling over into other instances of the tag like this?
    Is this a bug?

    -ScottA



  • On 21/03/2013 at 18:58, xxxxxxxx wrote:

    1. have you tried to reference to class instance explicitly on member allocation / use a constructor ?
    at least for me this is working for a somehwat compareable case.

    2. GetNodeData() is actually not listed as method for GeListNode in the python docs, so i am not
    sure if we are supposed to use it or how far it does work.



  • On 21/03/2013 at 19:40, xxxxxxxx wrote:

    This seems to fix the problem:

    class StorageTag(plugins.TagData) :  
      
      def __init__(self, tagListData=[]) :  
          super(StorageTag, self).__init__()  
          self.tagListData = tagListData
    

    It's strange that we are forced to use custom constructors to make lists work. But not other types of variables.

    -ScottA



  • On 21/03/2013 at 21:02, xxxxxxxx wrote:

    Argh!
    Nope.. Still doesn't work. 😠

    -ScottA



  • On 21/03/2013 at 21:43, xxxxxxxx wrote:

    do not pass lists as optional parameters, it will cause all instances to share the 
    same list if you skip the parameter on invoking the method. a possible workarround
    is the following :

    def __init__ (self, somelist = []) :
    self.foo = somelist[:]

    related thread



  • On 22/03/2013 at 00:41, xxxxxxxx wrote:

    Hi Scott,

    list objects are mutable. The list is created once at the place you wrote it. Every instance will have
    a reference to the exactly same list when you create it on class-level or as a default-argument (since
    those are created only once, too). One has to be very careful with mutable types within class
    instances. No value in Python is passed by value, they're always passed by reference. But strings
    and integers and others are immutable which creates the illusion of them being passed by
    value.

    Best,
    -Niklas



  • On 22/03/2013 at 08:30, xxxxxxxx wrote:

    Thanks Guys.
    That's got it working. 👍

    -ScottA


Log in to reply