Question

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?

Thanks!

Was it helpful?

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)

OTHER TIPS

Edit: Contrary to the initial assertion, the following doesn't exhibit the deepcopy behaviour which I missed in the description. Still, I'll leave it up in case there is anything useful.

A bit of a guess, but the following exhibits the behaviour you are encountering:

import random

width = 5
height = 5

class Brain(object):

    def __init__(self):
        self.w = [1]
        self.ix = [1]
        self.state = 1

    def mutate(self):
        self.state += 1

class Animal(object):
    brain = Brain()
    x = random.randint(0, width)
    y = random.randint(0,height)
    age = 0
    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.state
reproduce(parent)

for each in animals:
    print each.brain.state

When you run it, you get out:

1
2
2

If this is the case, the problem is because animal.brain is an attribute of the class, not the instance. The consequence of this is that all instances share the attribute.

It's simple to correct:

class Animal(object):
    x = random.randint(0, width)
    y = random.randint(0,height)
    age = 0
    fitness = 10

    def __init__(self):
        self.brain = Brain()

With that fix, you'll output the correct value for the parent, and the child will change. For example:

1
1
3

(according to how many times mutate() is called).

You'll also likely encounter similar problems with the other properties of Brain, but that can be an exercise for the reader.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top