Применение функции к объекту в Python

Я написал искусственный симулятор жизни. каждое существо является объектом класса "Животное", которое я определил, с некоторыми свойствами. Я определил функцию "воспроизведение" вне класса Animal:

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[i].brain = Brain(), "мутированная" часть в функции воспроизведения гарантирует, что мозг ребенка не идентичен мозгу родителя.

Однако проблема в том, что когда я применяю эту функцию к какому-либо животному из списка, у ребенка действительно появляется немного новый мозг, но мозг родителя становится идентичным новому мозгу ребенка. Когда я использую reproduce(copy.deepcopy(animals[i])) вместо reproduce(animals[i]) этого не происходит. Какова причина?

Спасибо!

2 ответа

Решение

Еще один удар, основанный на комментарии @ Армина. Это демонстрирует соответствующее поведение глубокой копии:

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

Исправление здесь состоит в том, чтобы не сохранять значения состояния в изменяемом типе, который вы копируете между объектами; в данном случае это список, но это может быть любой изменяемый объект.

Изменить: то, что вы делаете в исходном коде, это скопировать содержимое parent.brain.w в child.brain.w, У Python есть свойство, что присваивания исходному объекту, а не копии объекта или содержимого (если вы не используете copy модуль). Документы покрывают это хорошо. Вкратце, это означает следующее:

>>> 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

То есть оба a а также b один и тот же список. Это не совсем то, что вы делаете; вы копируете список в объект, но это эквивалентно:

>>> 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]

Если ваш тип не является изменяемым, то при его изменении создается новый объект. Рассмотрим, например, несколько эквивалентный список кортежей (которые являются неизменяемыми):

>>> 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)

Редактировать: В отличие от первоначального утверждения, следующее не демонстрирует поведение глубокой копии, которое я пропустил в описании. Тем не менее, я оставлю это на всякий случай, если есть что-то полезное.

Немного догадываюсь, но следующее показывает поведение, с которым вы сталкиваетесь:

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

Когда вы запустите его, вы получите:

1
2
2

Если это так, проблема заключается в том, что animal.brain является атрибутом класса, а не экземпляра. Следствием этого является то, что все экземпляры имеют общий атрибут.

Это просто исправить:

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

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

С этим исправлением вы выведете правильное значение для родителя, и потомок изменится. Например:

1
1
3

(в зависимости от того, сколько раз mutate() называется).

Вы также, вероятно, столкнетесь с аналогичными проблемами с другими свойствами Brain, но это может быть упражнением для читателя.

Другие вопросы по тегам