guojuw
guojuw

Reputation: 13

Creating new object instance but old instance was changed in python

Here I just copy my python code from my project:

from numpy.core.function_base import linspace
import numpy as np
from operator import mod



class contactPair:
    itr = 0
    rawData=[]
    rawDataLabel=[]

    uidi=0
    uidj=0
    Nc=0
    cLoc=[0,0,0]
    deltan=0
    deltatAcc=0
    Np=0
    Fi=[0,0,0]
    Fni=[0,0,0]
    Fti=[0,0,0]
    n=[0,0,0]
    t=[0,0,0]
    RVCP=[0,0,0]

    def __init__(self,itr,label,cntinfo):
        self.itr=itr
        self.rawData=cntinfo
        self.rawDataLabel=label
        self.uidi=cntinfo[0]
        self.uidj=cntinfo[1]
        self.Nc=cntinfo[2]
        self.deltan=cntinfo[6]
        self.deltatAcc=cntinfo[7]
        self.Np=cntinfo[8]
        for k in range(3):
            self.cLoc[k]=cntinfo[3+k]
            self.Fi[k]=cntinfo[9+k]
            self.Fni[k]=cntinfo[12+k]
            self.Fti[k]=cntinfo[15+k]
            self.n[k]=cntinfo[18+k]
            self.t[k]=cntinfo[21+k]
            self.RVCP[k]=cntinfo[24+k]
        g=1


class par:
    name = 0
    itr = 0
    uid = 0
    rawData = []
    rawDataLabel = []

    mass = 0
    pos = [0,0,0]
    vel = [0,0,0]
    acc = [0,0,0]
    acc2 = [0,0,0]
    hf =[0,0,0]
    theta=[0,0,0]
    omega=[0,0,0]
    alpha=[0,0,0]
    alpha2=[0,0,0]
    dia=[0,0,0]
    Nc=0
    ppf=[0,0,0]
    rotMat=[]
    vol=0
    density=0
    mofi=[]
    normalUnit=[]

    angleToShearingPlane=0
    def __init__(self,itr,label,parinfo):
        #self.name=0
        self.itr=itr
        self.rawData=parinfo
        self.rawDataLabel=label
        self.name=parinfo[0]
        self.mass=parinfo[2]
        for k in range(3):
            self.pos[k]= parinfo[3+k]
            self.vel[k]= parinfo[6+k]
            self.acc[k]= parinfo[9+k]
            self.acc2[k]= parinfo[12+k]
            self.hf[k]= parinfo[15+k]
            self.theta[k]=parinfo[18+k]
            self.omega[k]=parinfo[21+k]
            self.alpha[k]=parinfo[24+k]
            self.alpha2[k]=parinfo[27+k]
            self.dia[k]= parinfo[33+k]
            self.ppf[k]= parinfo[39+k]
        self.uid =  parinfo[36]
        self.Nc =  parinfo[37]
        
        #ipDir1,ipDir2,ipDir3,mat=rotate(self.theta[0],self.theta[1],self.theta[2],1,0,0) 
        #self.rotMat=mat
        #self.normalUnit=[ipDir1,ipDir2,ipDir3]
        self.angleToShearingPlane=mod(self.theta[2]+2*np.pi,np.pi)
        #self.angleToShearingPlane=mod(self.theta[1]+2*np.pi,np.pi)
        


t1=linspace(1,100,100)
c1=contactPair(1,[],t1)
p1=par(1,[],t1)

print(f"c1.cLoc: {c1.cLoc}")
print(f"p1.acc: {p1.acc}")

t2=linspace(101,200,100)
c2=contactPair(2,[],t2)
p2=par(1,[],t2)

print(f"c2.cLoc: {c2.cLoc}")
print(f"p2.acc: {p2.acc}")
print(f"c1.cLoc: {c1.cLoc}")
print(f"p1.acc: {p1.acc}")

and what I got from the python console is

c1.cLoc: [4.0, 5.0, 6.0]
p1.acc: [10.0, 11.0, 12.0]
c2.cLoc: [104.0, 105.0, 106.0]
p2.acc: [110.0, 111.0, 112.0]
c1.cLoc: [104.0, 105.0, 106.0]
p1.acc: [110.0, 111.0, 112.0]

What I did is

  1. defining two classes, i.e., contactPair and par.
  2. creating two new instances c1, p1 based on input t1.
  3. creating another two new instances c2, p2 based on input t2.

I don't know why the values of c1 and p1 are changed after creating c2 and p2.

Upvotes: 1

Views: 1414

Answers (1)

Matt Miguel
Matt Miguel

Reputation: 1375

It's because you define everything as class variables instead of instance variables.

Focusing on cLoc inside of contactPair for example - you define it is a class variable that belongs to the entire class contactPair instead of belonging to an instance. This means that every instance of this class actually shares the same cLoc value. You don't want this to be the case, so you should instead have each instance create it's own version of the cLoc variable inside the __init__ constructor and assign it as an attribute of self.

Let me do a simple example illustrating this.

>>> class A():        
...     class_var = []                                  
...     def __init__(self):                             
...         self.instance_var = []                      
...     def append_more(self,x):                        
...         self.class_var.append(x)                    
...         self.instance_var.append(x)                 
...                                                     
>>> a = A()                                             
>>> b = A()                                             
>>> a.append_more(1)                                    
>>> b.append_more(2)                                    
>>> a.instance_var                                      
[1]                                                     
>>> b.instance_var                                      
[2]                                                     
>>> a.class_var                                         
[1, 2]                                                  
>>> b.class_var                                         
[1, 2]                                                  
>>> A.class_var                                         
[1, 2]                                                  
>>> A.instance_var                                      
Traceback (most recent call last):                        
File "<stdin>", line 1, in <module>                   
AttributeError: type object 'A' has no attribute 'instance_var'                                                 >>>

The class_var variable actually belongs to the class A, not to instances a or b. Executing a.class_var.append(x) basically results in python doing the following:

  • check if "class_var" exists in the object namespace for a - it does not
  • check if "class_var" exists in the object namespace for a.__class__ (which is A) - it does
  • take the a.__class__.class_var that was found and call .append(x) on it

This basically means the expressions a.class_var or b.class_var refer to the same object as A.class_var.

But there was no instance_var variable created in the class context for A. That gets created in the __init__ constructor for specific instances and gets assigned to the instance not the class. This means that each instance has a unique list bound to instance_var in their object namespace.

Referring to a.instance_var causes python to find something at the instance level and not go and check the class level.

Note that if you were to overwrite class_var for a specific instance, you would basically cause the instance to point to some other object besides the class variable. This would not affect the original class variable or any other instance that did not do a similar "overwrite". For example, continuing the code above:

>>> a.class_var = [] # this makes a class_var reference to a new unrelated list at the instance level in a                              
>>> a.class_var                                         
[]                                                      
>>> b.class_var                                         
[1, 2]                                                  
>>> A.class_var                                         
[1, 2]                                                  
>>> a.append_more(3) # will use new fresh unrelated list                                   
>>> b.append_more(4) # will use original list belonging to class                                  
>>> a.class_var                                         
[3]                                                     
>>> b.class_var                                         
[1, 2, 4]                                               
>>> A.class_var                                         
[1, 2, 4]                                               
>>>

Upvotes: 1

Related Questions