Мелкая копия, почему список не меняется - PullRequest
4 голосов
/ 25 декабря 2010

Я пытаюсь понять разницу между мелкой копией и глубокой копией в python. Я прочитал много постов здесь, и они были полезны. Тем не менее, я до сих пор не понимаю разницу. Может кто-нибудь, пожалуйста, объясните причину результата ниже. Результат, который я не понимаю, указан в комментариях.

Большое спасибо.

import copy
import random

class understand_copy(object):
    def __init__(self):
        self.listofvalues = [4, 5]

    def set_listofvalues(self, pos, value):
        self.listofvalues[pos] = value

ins = understand_copy()

newins = copy.copy(ins)

newins.set_listofvalues(1,3)
print "ins = ", ins.listofvalues
print "in newins", newins.listofvalues
# Gives the following output as I would expect based on the explanations.
# prints ins = [4, 3]
# prints newins = [4, 3]


newins.listofvalues.append(5)
print "ins =", ins.listofvalues
print "newins =", newins.listofvalues
# Gives the following output as I would expect based on the explanations.
# prints ins = [4, 3, 5]
# prints newins = [4, 3, 5]


newins.listofvalues = [10, 11]
print "ins = ", ins.listofvalues
print "newins = ", newins.listofvalues
# Gives
# ins = [4, 3, 5]
# newins = [10, 11]
# This is the output that I do not understand. 
# Why does ins.listofvalues not change this case.**

Ответы [ 2 ]

7 голосов
/ 25 декабря 2010

В Python поле объекта сохраняет ссылку на объект. Поэтому, когда вы назначаете новый список в своем примере, вы изменяете объект, на который ссылается поле, а не его содержимое. До воздействия свойство listofvalues обоих объектов ссылается на один и тот же список, но после воздействия они ссылаются на два разных списка.

Это эквивалентно следующему коду:

>>> a = [4, 5]
>>> b = a
>>> b.append(3)
>>> b
[4, 5, 3]
>>> a
[4, 5, 3]
>>> b = [6, 7]
>>> b
[6, 7]
>>> a
[4, 5, 3]

Если вы хотите изменить содержимое списка, а не ссылку, вам нужно использовать нарезку. То есть:

>>> a = [4, 5, 3]
>>> b = a
>>> b[:] = [6, 7]
>>> b
[6, 7]
>>> a
[6, 7]

ПРИМЕЧАНИЕ: следующее основано на моем понимании внутренних элементов Python 2.6. Таким образом, он действительно специфичен для реализации, однако ментальная модель, которую он вам дает, вероятно, довольно близка к тому, как написано правило языка, и должна работать с любой реализацией.

В Python доступ к объектам всегда осуществляется через ссылки (как в Java, а не в C ++). Однако имя переменной или имени атрибута может быть, однако, как привязка в словаре и реализовано как таковое в CPython (за исключением оптимизации локальной переменной, наличия __slots__ или псевдоатрибутов, предоставляемых через __getattr__ и друзья).

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

Итак, в вашем примере вы ведете себя так же, как в следующем коде:

def understand_copy():
   return {'listofvalues': [4, 5]}

def deepcopy(obj):
   if instance(obj, dict):
       copy = {}
       for key, value in obj.iteritems():
           copy[key] = deepcopy(value)  # Note the recursion
       return copy
   if instance(obj, list):
       copy = []
       for value in obj:
           copy.append(deepcopy(value)) # Note the recursion
       return copy
   return obj

def copy(obj):
   if instance(obj, dict):
       copy = {}
       for key, value in obj.iteritems():
           copy[key] = value  # No recursion this time, the copy is shallow
       return copy
   if instance(obj, list):
       copy = []
       for value in obj:
           copy.append(value) # No recursion this time, the copy is shallow
       return copy
   return obj

globals = {}
globals['ins'] = understand_copy()
globals['new'] = copy(global['ins'])
# Now globals['ins']['listofvalues']
# and globals['new']['listofvalues']
# reference the same object!

globals['ins']['listofvalues'].__setitem__(0, 3)
globals['ins']['listofvalues'].append(5)
# We are calling function on one object,
# but not changing a binding, so the two
# 'listofvalues' attribute still refers
# to the same object.

globals['ins']['listofvalues'] = [10, 11]
# Now we changed the binding of the name
# in the dictionary 'ins' so now the two
# objects attributes points to different
# lists.
3 голосов
/ 25 декабря 2010

ins.listofvalues ​​не изменяется, поскольку вы заменили атрибут newins новым списком, в то время как append () не заменяет объект, изменяя его. Следовательно, два разных атрибута теперь указывают на разные списки.

Вы можете получить тот же эффект без копирования:

>>> ins = [1,2,3]
>>> newins = ins
>>> 
>>> ins.append(4)
>>> newins
[1, 2, 3, 4]
>>> 
>>> ins = [5,6,7]
>>> newins
[1, 2, 3, 4]

Таким образом, вы правильно понимаете copy / deepcopy, проблема в том, что переменные в Python работают неправильно. Это ссылки, но они , а не указатели. Обычно лучше понимать это как «ярлыки». В этом случае вы пометили исходный список как ins.listofvalues, а затем скопировали этот ярлык в newins.listofvalues. Но когда вы переназначаете ins.listofvalues, вы прикрепляете эту метку к новому объекту.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...