Назначение объектов и основных типов

Есть этот код:

# assignment behaviour for integer
a = b = 0
print a, b # prints 0 0
a = 4
print a, b # prints 4 0 - different!

# assignment behaviour for class object
class Klasa:
    def __init__(self, num):
        self.num = num

a = Klasa(2)
b = a
print a.num, b.num # prints 2 2
a.num = 3
print a.num, b.num # prints 3 3 - the same!

Вопросы:

  • Почему оператор присваивания работает по-разному для фундаментального типа и объект класса (для основных типов он копирует по значению, для объекта класса он копирует по ссылке)?
  • Как скопировать объекты класса только по значению?
  • Как сделать ссылки на основные типы, например, в С++ int & b = a?

Ответ 1

Это камнем преткновения для многих пользователей Python. Семантика ссылки объекта отлична от того, к чему привыкли программисты C.

Возьмем первый случай. Когда вы говорите a = b = 0, создается новый объект int со значением 0 и создаются две ссылки на него (один - a, а другой - b). Эти две переменные указывают на один и тот же объект (целое, которое мы создали). Теперь мы запускаем a = 4. Создается новый объект int значения 4, и для этого указывается a. Это означает, что количество ссылок на 4 равно единице, а количество ссылок на 0 сокращено на единицу.

Сравните это с a = 4 в C, на который записана область памяти, в которой a "указывает". a = b = 4 в C означает, что 4 записывается в две части памяти: одну для a и другую для b.

Теперь второй случай, a = Klass(2) создает объект типа Klass, увеличивает счетчик ссылок на единицу и указывает на него a. b = a просто принимает то, на что указывает a, делает b указывать на одно и то же и увеличивает счетчик ссылок на единицу. Это то же самое, что и в случае, если вы сделали a = b = Klass(2). Попытка печати a.num и b.num одинакова, поскольку вы разыскиваете один и тот же объект и печатаете значение атрибута. Вы можете использовать встроенную функцию id, чтобы увидеть, что объект тот же (id(a) и id(b) вернет тот же идентификатор). Теперь вы меняете объект, присваивая значение одному из его атрибутов. Поскольку a и b указывают на один и тот же объект, вы ожидаете, что изменение значения будет видимым при доступе к объекту через a или b. И это именно так.

Теперь, для ответов на ваши вопросы.

  • Оператор присваивания не работает по-разному для этих двух. Все, что он делает, это добавить ссылку на RValue и сделать для нее значение LValue. Он всегда "по ссылке" (хотя этот термин имеет больше смысла в контексте передачи параметров, чем простые назначения).
  • Если вы хотите копировать объекты, используйте модуль копирования.
  • Как я сказал в пункте 1, когда вы выполняете задание, вы всегда меняете ссылки. Копирование никогда не выполняется, если вы не попросите об этом.

Ответ 2

Цитата из Модель данных

Объекты - это абстракция Pythons для данных. Все данные в Python программа представлена ​​объектами или отношениями между объектами. (В смысл и в соответствии с моделью фон Нейманн "сохраненной программный компьютер", код также представлен объектами.)

С точки зрения Python Фундаментальный тип данных принципиально отличается от C/С++. Он используется для сопоставления типов данных C/C++ с Python. Поэтому давайте пока не будем обсуждать это обсуждение и рассмотрим тот факт, что все данные являются объектами и являются проявлением некоторого класса. Каждый объект имеет идентификатор (несколько похожий адрес), значение и тип.

Все объекты копируются по ссылке. Для ex

>>> x=20
>>> y=x
>>> id(x)==id(y)
True
>>>

Единственный способ создать новый экземпляр - создать его.

>>> x=3
>>> id(x)==id(y)
False
>>> x==y
False

Это может показаться сложным в первом экземпляре, но для упрощения бит Python сделал некоторые типы неизменяемыми. Например, вы не можете изменить string. Вы должны разрезать его и создать новый строковый объект.

Часто копирование по ссылке дает неожиданные результаты для ex.

x=[[0]*8]*8 может дать вам ощущение, что он создает двумерный список 0 s. Но на самом деле он создает список ссылок того же объекта списка [0] s. Таким образом, x [1] [1] в конечном итоге изменит все дубликаты экземпляра одновременно.

Модуль Copy предоставляет метод под названием deepcopy для создания нового экземпляра объекта, а не мелкого экземпляра. Это полезно, когда вы намереваетесь иметь два разных объекта и манипулировать им отдельно так же, как и в своем втором примере.

Чтобы расширить свой пример

>>> class Klasa:
    def __init__(self, num):
         self.num = num


>>> a = Klasa(2)
>>> b = copy.deepcopy(a)
>>> print a.num, b.num # prints 2 2
2 2  
>>> a.num = 3
>>> print a.num, b.num # prints 3 3 - different!
3 2

Ответ 3

Это не работает по-другому. В первом примере вы изменили a так, чтобы a и b ссылались на разные объекты. В вашем втором примере вы этого не сделали, поэтому a и b все еще ссылаются на один и тот же объект.

Целые числа, между прочим, неизменяемы. Вы не можете изменить их значение. Все, что вы можете сделать, это сделать новое целое число и переустановить вашу ссылку. (как вы делали в первом примере)

Ответ 4

Предположим, у вас и у меня есть общий друг. Если я решаю, что я больше не люблю ее, она по-прежнему твоя подруга. С другой стороны, если я дам ей подарок, твой друг получил подарок.

Назначение не копирует ничего в Python, а "копировать по ссылке" находится где-то между неудобным и бессмысленным (как вы на самом деле указываете в одном из своих комментариев). Присвоение заставляет переменную начинать ссылаться на значение. В Python нет отдельных "фундаментальных типов"; в то время как некоторые из них встроены, int по-прежнему является классом.

В обоих случаях присваивание приводит к тому, что переменная ссылается на то, что она оценивает по правую сторону. Поведение, которое вы видите, именно то, что вы должны ожидать в этой среде, в метафоре. Независимо от того, является ли ваш "друг" int или Klasa, присвоение атрибуту принципиально отличается от переназначения переменной на совершенно другой экземпляр с соответствующим поведением.

Единственное реальное отличие состоит в том, что int не имеет атрибутов, которые вы можете назначить. (Это часть, где реализация на самом деле должна сделать немного магии, чтобы ограничить вас.)

Вы смешиваете две разные концепции "ссылки". С++ T& - волшебная вещь, которая при назначении обновляет упомянутый объект на месте, а не саму ссылку; который никогда не может быть "повторно включен" после инициализации ссылки. Это полезно на языке, где большинство вещей являются значениями. В Python все начинается с начала. Ссылка Pythonic больше похожа на всегда действующий, никогда не нулевой, не используемый для арифметики, автоматически разыменованный указатель. Присвоение заставляет ссылку начинать со ссылкой на другую вещь полностью. Вы не можете "обновить выделенный объект на месте", заменив его оптовой, потому что объекты Python просто не работают. Вы можете, конечно, обновить свое внутреннее состояние, играя с его атрибутами (если есть какие-либо доступные), но эти атрибуты сами по себе также содержат все ссылки.