Проблемы с пониманием передачи значений и ссылок в Python - PullRequest
3 голосов
/ 20 октября 2011

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

class person:
    age = 21

class bar:
    def __init__(self, arg1):
        self.foo = arg1
        self.foo.age = 23

def baz(arg1):
    arg1.age = 27

def teh(arg1):
    arg1 = [3,2,1]

Person1 = person()
bar1 = bar(Person1)

print Person1.age
print bar1.foo.age

baz(Person1)

print Person1.age
print bar1.foo.age

meh = [1,2,3]
teh(meh)
print meh

Вывод:

23
23
27
27
[1, 2, 3]

Поэтому, когда мы объявляем Person1, Person1.age равен 21. Ссылка на этот объект передается вконструктор класса другого экземпляра класса bar, называемый bar1.Любые изменения, внесенные в эту ссылку, изменят Person1.

Это также тот случай, когда мы передаем Person1 в нормальную функцию, Person1.age теперь равен 27.

Но почему это не так?работать с переменной "Мех"?Конечно, если мы назначим переменную a = meh и изменим a = [6, 6, 6], то meh также будет изменен.Я не совсем понимаю.Есть ли литература о том, как все это работает?

Ответы [ 3 ]

8 голосов
/ 20 октября 2011

Я вижу три фундаментальные концепции Python, которые могут пролить свет на вопрос:

1) Во-первых, присвоение из изменяемого объекта, как в

self.foo = arg1

, похоже на копированиеуказатель (а не значение, на которое указывает): self.foo и arg1 - это один и тот же объект.Вот почему следующая строка

self.foo.age = 23

изменяет arg1 (то есть Person1). Таким образом, переменные - это разные «имена» или «метки», которые могут указывать на уникальный объект (здесь объект person).Это объясняет, почему baz(Person1) изменяет Person1.age и bar1.foo.age на 27, поскольку Person1 и bar1.foo - это просто два имени для одного и того же person объекта (Person1 is bar1.foo возвращает True, в Python).

2) Вторым важным понятием является назначение.В

def teh(arg1):
    arg1 = [3,2,1]

переменная arg1 является локальной, поэтому код

meh = [1,2,3]
teh(meh)

сначала делает arg1 = meh, что означает, что arg1 является дополнительным (локальным) именем длясписок meh;но делать arg1 = [3, 2, 1] - все равно что сказать: «Я передумал: с этого момента arg1 будет именем нового списка [3, 2, 1]».Здесь важно помнить, что назначения, несмотря на то, что они обозначены знаком «равно», являются асимметричными : они дают (изменяемому) объекту справа и справа дополнительное имя,дано в левой части (поэтому вы не можете сделать [3, 2, 1] = arg1, поскольку в левой части должно быть имя [или имена]).Таким образом, arg1 = meh; arg1 = [3, 2, 1] не может измениться meh.

3) Последний пункт связан с названием вопроса: «передача по значению» и «передача по ссылке» не являются понятиями, которые актуальны в Python.Соответствующими понятиями являются « изменяемый объект » и « неизменяемый объект ».Списки изменчивы, а числа нет, что объясняет то, что вы наблюдаете.Кроме того, ваши объекты Person1 и bar1 являются изменяемыми (поэтому вы можете изменить возраст человека).Вы можете найти больше информации об этих понятиях в текстовом руководстве и видеоуроке .В Википедии также есть некоторая (более техническая) информация .Пример иллюстрирует различие в поведении между изменяемым и неизменным:

x = (4, 2)
y = x  # This is equivalent to copying the value (4, 2), because tuples are immutable
y += (1, 2, 3)  # This does not change x, because tuples are immutable; this does y = (4, 2) + (1, 2, 3)

x = [4, 2]
y = x  # This is equivalent to copying a pointer to the [4, 2] list
y += [1, 2, 3]  # This also changes x, because x and y are different names for the same (mutable) object

Последняя строка не эквивалентна y = y + [1, 2, 3], потому что это только поместит новый объект списка в переменную y вместо изменения списка, на который ссылаются как y, так и x.

Три вышеприведенных понятия (переменные как имена [для изменяемых объектов], асимметричное назначение и изменчивость / неизменность) объясняют многие из Pythonповедения.

6 голосов
/ 20 октября 2011

Конечно, если мы назначим переменную a = meh и изменим a = [6, 6, 6], то meh также будет изменен.

Нет, фактически, он выиграл't:

>>> meh = [1,2,3]
>>> a = meh
>>> a = [6, 6, 6]
>>> print a
[6, 6, 6]
>>> print meh
[1, 2, 3]

Это разница между перезаписью переменной и изменением экземпляра , указывающего на переменной.

Списки, словари, наборы и объекты являются изменяемыми типами.Если вы добавляете, удаляете, устанавливаете, получаете или иным образом изменяете что-то в экземпляре из них, оно обновляет все, что ссылается на этот экземпляр.

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


a = [1,2,3] # New instance
a[1] = 4    # Modifying existing instance

b = {'x':1, 'y':2} # New instance
b['x'] = 3         # Modifying existing instance

self.x = [1,2,3] # Modifying existing object instance pointed to by 'self',
                 # and creating new instance of a list to store in 'self.x'

self.x[0] = 5    # Modifying existing list instance pointed to by 'self.x'
0 голосов
/ 20 октября 2011

Python не имеет ни передачи по значению, ни передачи по ссылке, но вместо этого использует передачу по объектам - другими словами, объекты передаются непосредственно в функции и привязываются к имени параметра, указанному вопределение функции.

Когда вы делаете spam = "green", вы привязываете спам с именем к строковому объекту "green";если вы затем делаете яйца = спам, вы ничего не копировали, вы не делали ссылочных указателей;Вы просто связали другое имя, яйца, с тем же объектом (в данном случае «зеленым»).Если вы затем связываете спам с чем-то другим (spam = 3.14159), яйца по-прежнему будут привязаны к «зеленому».

В вашей функции teh вы не изменяете, не модифицируете и не изменяете переданный объектпереназначение имени arg1 в другой список.Чтобы изменить arg1 вы хотите это:

def teh(arg1):
    arg1[:] = [3, 2, 1]
...