Search code examples
pythonpython-2.7oopmutable

Python-2.7 class method causing objects to be


I'm having trouble with a class definition that I have written that causes an object that calls a class method to be changed. This behavior of changing the object is undesirable. I would like the class method to be able to add two objects together and spit out a new object of the same type i.e. a = b+c

My class definition for the addition creates a new instance of the class type to hold the results of the combination of the two objects. The trouble is that upon completion the calling object is altered to be the same as the sum of the two objects.

A minimal working example of the undesirable behavior is shown below. The code creates two instances of a class and initializes them with some random data. Each object contains two equal length lists and an integer that holds the lengths of the lists. Then main code prints the contents of each object, calls the addition method and stores the data in a new object, and finally prints the contents of the original objects.

I have run into an issue with default mutable arguments when I was creating the initialization for the class. I'm thinking that this is another case of me not understanding when mutable behavior occurs in Python code and how to prevent it.

class myClass:

        def __init__(self,list_a=None,list_b=None,list_count=0):
                self.list_a=[] if list_b is None else list_a
                self.list_b=[] if list_b is None else list_b
                self.list_count = list_count

        def add_data(self,ElementA,ElementB):
                self.list_a.append(ElementA)
                self.list_b.append(ElementB)
                self.list_count = self.list_count + 1

        def print_data(self):
                print("Number of elements in this object is: %d"%self.list_count)
                print("List A contents\n%s"%self.list_a)
                print("List B contents\n%s\n"%self.list_b)

        def addTogether(self,other):
                test_data = myClass(self.list_a,self.list_b,self.list_count)

                for i in range(0,other.list_count):
                        test_data.add_data(other.list_a[i],other.list_b[i])

                return(myClass(test_data.list_a,test_data.list_b,test_data.list_count))

import random

object_array = [myClass() for i in range(2)]
#Loop over object array and fill each object with some data
for i in range(0,2):

        NumData = int(10*random.random())
        for m in range(0,NumData):
                #Generate some junk data to insert into the list of this object
                ListAData = int(10*random.random())
                ListBData = int(10*random.random())

                object_array[i].check_data()
                object_array[i].add_data(ListAData,ListBData)
                object_array[i].check_data()

object_array[0].print_data()
object_array[1].print_data()

new_data = object_array[0].addTogether(object_array[1])
new_data.print_data()

object_array[0].print_data()
object_array[1].print_data()

Output from code:

Number of elements in this object is: 2
List A contents
[6, 5]
List B contents
[8, 7]

Number of elements in this object is: 2
List A contents
[7, 6]
List B contents
[5, 3]

Number of elements in this object is: 4
List A contents
[6, 5, 7, 6]
List B contents
[8, 7, 5, 3]

Number of elements in this object is: 2
List A contents
[6, 5, 7, 6]
List B contents
[8, 7, 5, 3]

Number of elements in this object is: 2
List A contents
[7, 6]
List B contents
[5, 3]

Solution

  • To "defend" incoming lists, which are only bunched-together references to objects, simply shallow-copy them on construction:

    self.ListA=[] if ListA is None else ListA[:]
    self.ListB=[] if ListB is None else ListB[:]
    

    Note that mylist[:] duplicates the list (not the inside objects though, so that's a shallow copy), and defends whatever outer references to the source list that existed.

    Currently you're altering the lists you're given on construction.

    Example:

    List A contents
    [1, 7, 6, 9, 8, 8, 5, 0]
    List B contents
    [8, 9, 3, 5, 1, 3, 1, 8]
    
    Number of elements in this object is: 8
    List A contents
    [7, 3, 1, 3, 9, 2, 8, 0]
    List B contents
    [8, 6, 6, 5, 0, 1, 8, 9]
    
    Number of elements in this object is: 16
    List A contents
    [1, 7, 6, 9, 8, 8, 5, 0, 7, 3, 1, 3, 9, 2, 8, 0]
    List B contents
    [8, 9, 3, 5, 1, 3, 1, 8, 8, 6, 6, 5, 0, 1, 8, 9]
    
    Number of elements in this object is: 8
    List A contents
    [1, 7, 6, 9, 8, 8, 5, 0]
    List B contents
    [8, 9, 3, 5, 1, 3, 1, 8]
    
    Number of elements in this object is: 8
    List A contents
    [7, 3, 1, 3, 9, 2, 8, 0]
    List B contents
    [8, 6, 6, 5, 0, 1, 8, 9]