pythonclassartificial-life

applying a function on an object in python


I wrote an artificial life simulation. each creature is an object of the class "Animal" that i defined, with some properties. I defined a function "reproduce" outside the Animal class:

def reproduce(parent):
    child = Animal()
    child.brain.w= parent.brain.w[:]
    child.brain.ix= parent.brain.ix[:]
    child.x,child.y = random.randint(0,width),random.randint(0,height)
    child.age = 0
    child.fitness= 9 + parent.fitness/10 #parent.fitness/2

    mutation = random.choice([0,1,1,1,1,1,1,1,1,2,3,4,5])
    for b in range(mutation):
      child.brain.mutate()
    animals.append(child)

As can be seen, each animal has a brain, which is an object from different class: for each animal I defined animals[i].brain = Brain(). the "mutate" part in the reproduce function ensures that the child's brain is not identical to the parent's brain.

However, the problem is that when I apply this function on some animal from the list, the child indeed get slightly new brain, but the parent's brain becomes identical to the child's new brain. When I use reproduce(copy.deepcopy(animals[i])) instead of reproduce(animals[i]) that does not happen. What is the reason?


Solution

  • Another stab based on @Armin's comment. This does exhibit the relevant deepcopy behaviour:

    import random
    
    width = 5
    height = 5
    
    class Brain(object):
    
        def __init__(self):
            self.w = [[1]]
            self.ix = [[1]]
    
        def mutate(self):
            self.w[0].append(1)
    
    class Animal(object):
    
        def __init__(self):
            self.brain = Brain()
            self.x = random.randint(0, width)
            self.y = random.randint(0, height)
            self.age = 0
            self.fitness = 10
    
    def reproduce(parent):
        child = Animal()
        child.brain.w= parent.brain.w[:]
        child.brain.ix= parent.brain.ix[:]
        child.x,child.y = random.randint(0,width),random.randint(0,height)
        child.age = 0
        child.fitness= 9 + parent.fitness/10 #parent.fitness/2
    
        mutation = random.choice([0,1,1,1,1,1,1,1,1,2,3,4,5])
        for b in range(mutation):
          child.brain.mutate()
        animals.append(child)
    
    animals = []
    parent = Animal()
    
    animals.append(parent)
    print parent.brain.w
    #reproduce(parent)
    import copy
    reproduce(copy.deepcopy(parent))
    
    for each in animals:
        print each.brain.w
    

    The fix here is to not have the state values stored in a mutable type that you're copying between objects; in this case a list, but it could be any mutable object.

    Edit: What you're doing in the original code is to copy the contents of parent.brain.w into child.brain.w. Python has the property that assignments are to the original object, not copies of the object or the contents (unless you use the copy module). The docs cover this well. Tersely, this means the following is true:

    >>> a = [1, 2, 3, 4, 5]
    >>> b = a
    >>> b.append(6)
    >>> b
    [1, 2, 3, 4, 5, 6]
    >>> a
    [1, 2, 3, 4, 5, 6]
    >>> a is b
    True
    

    That is, both a and b are the same list. That isn't quite what you are doing; you're copying a list into an object, but that is equivalent:

    >>> a = [[1, 2, 3]]
    >>> b = []
    >>> b = a[:] # What you are doing
    >>> b is a
    False
    >>> b[0] is a[0]
    True
    >>> b[0].append(4)
    >>> b[0]
    [1, 2, 3, 4]
    >>> a[0]
    [1, 2, 3, 4]
    

    If your type is not mutable, then when you modify it, a new object is created. Consider, for example, a somewhat equivalent list of tuples (which are immutable):

    >>> a = [(1, 2, 3)]
    >>> b = []
    >>> b = a[:]
    >>> b is a
    False
    >>> b[0] is a[0] # Initially the objects are the same
    True
    >>> b[0] += (4,) # Now a new object is created and overwrites b[0]
    >>> b[0] is a[0]
    False
    >>> b[0]
    (1, 2, 3, 4)
    >>> a[0]
    (1, 2, 3)