Ссылки на базовые данные Python, список одинаковых ссылок

Скажем, у меня есть два списка:

>>> l1=[1,2,3,4] >>> l2=[11,12,13,14] 

Я могу поместить эти списки в кортеж или словарь, и кажется, что все они ссылаются на исходный список:

 >>> t=(l1,l2) >>> d={'l1':l1, 'l2':l2} >>> id(l1)==id(d['l1'])==id(t[0]) True >>> l1 is d['l1'] is t[0] True 

Поскольку они являются ссылками, я могу изменить l1 и соответствующие данные в кортеже и словаре изменить соответственно:

 >>> l1.append(5) >>> l1 [1, 2, 3, 4, 5] >>> t ([1, 2, 3, 4, 5], [11, 12, 13, 14]) >>> d {'l2': [11, 12, 13, 14], 'l1': [1, 2, 3, 4, 5]} 

Включая, если я добавлю ссылку в словаре d или изменяемую ссылку в кортеже t :

 >>> d['l1'].append(6) >>> t[0].append(7) >>> d {'l2': [11, 12, 13, 14], 'l1': [1, 2, 3, 4, 5, 6, 7]} >>> l1 [1, 2, 3, 4, 5, 6, 7] 

Если теперь я устанавливаю l1 в новый список, счетчик ссылок для исходного списка уменьшается:

 >>> sys.getrefcount(l1) 4 >>> sys.getrefcount(t[0]) 4 >>> l1=['new','list'] >>> l1 is d['l1'] is t[0] False >>> sys.getrefcount(l1) 2 >>> sys.getrefcount(t[0]) 3 

И добавление или изменение l1 не изменяет d['l1'] или t[0] так как теперь это новая ссылка. Понятие косвенных ссылок довольно хорошо описано в документах Python, но не полностью.

Мои вопросы:

  1. Является ли изменчивый объект всегда ссылкой? Можете ли вы всегда предположить, что его изменение изменяет оригинал (если вы специально не делаете копию с l2=l1[:] вид идиомы)?

  2. Могу ли я собрать список всех тех же ссылок в Python? т.е. некоторая функция f(l1) которая возвращает ['l1', 'd', 't'] если все они относятся к одному и тому же списку?

  3. Я полагаю, что независимо от того, что данные будут оставаться действительными, пока есть какая-то ссылка на него.

то есть:

 l=[1,2,3] # create an object of three integers and create a ref to it l2=l # create a reference to the same object l=[4,5,6] # create a new object of 3 ints; the original now referenced # by l2 is unchanged and unmoved 

1) Изменение изменчивого объекта посредством ссылки всегда будет изменять «оригинал». Честно говоря, это предает непонимание ссылок. Новая ссылка – это как раз и «оригинал», как любая другая ссылка. Пока оба имени указывают на один и тот же объект, изменение объекта через любое имя будет отражено при доступе через другое имя.

2) Не совсем так, как вы хотите. gc.get_referrers возвращает все ссылки на объект.

 >>> l = [1, 2] >>> d = {0: l} >>> t = (l, ) >>> import gc >>> import pprint >>> pprint.pprint(gc.get_referrers(l)) [{'__builtins__': <module '__builtin__' (built-in)>, '__doc__': None, '__name__': '__main__', '__package__': None, 'd': {0: [1, 2]}, 'gc': <module 'gc' (built-in)>, 'l': [1, 2], 'pprint': <module 'pprint' from '/usr/lib/python2.6/pprint.pyc'>, 't': ([1, 2],)}, # This is globals() {0: [1, 2]}, # This is d ([1, 2],)] # this is t 

Обратите внимание, что фактический объект, на который ссылается l , не включен в возвращенный список, потому что он не содержит ссылку на себя. globals() возвращается, потому что в нем содержится ссылка на исходный список.

3) Если по действительной, вы имеете в виду «не будет собирать мусор», то это правильно, что запрещает очень маловероятную ошибку. Было бы очень жалким сборщиком мусора, который «украл» ваши данные.

Каждая переменная в Python является ссылкой.

Для списков вы сосредотачиваетесь на результатах метода append() и теряете взгляд на большую картину структур данных Python. В списках есть и другие методы, и есть преимущества и последствия для построения списка. Полезно подумать о списке как о представлении других объектов, упомянутых в списке. Они не «содержат» ничего, кроме правил и способов доступа к данным, на которые ссылаются объекты внутри них.

Метод list.append(x) специально эквивалентен l[len(l):]=[list]

Так:

 >>> l1=range(3) >>> l2=range(20,23) >>> l3=range(30,33) >>> l1[len(l1):]=[l2] # equivalent to 'append' for subscriptable sequences >>> l1[len(l1):]=l3 # same as 'extend' >>> l1 [0, 1, 2, [20, 21, 22], 30, 31, 32] >>> len(l1) 7 >>> l1.index(30) 4 >>> l1.index(20) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: list.index(x): x not in list >>> 20 in l1 False >>> 30 in l1 True 

Поместив конструктор списка вокруг l2 в l1[len(l1):]=[l2] или вызвав l.append(l2) , вы создаете ссылку, привязанную к l2. Если вы измените l2, ссылки также покажут изменение. Длина этого в списке – это один элемент – ссылка на прилагаемую последовательность.

Без ярлыка конструктора, как в l1[len(l1):]=l3 , вы копируете каждый элемент последовательности.

Если вы используете другие распространенные методы списка, такие как l.index(something) , или вы не найдете элементов внутри ссылок данных. l.sort() не будет сортироваться должным образом. Это «мелкие» операции над объектом, и с помощью l1[len(l1):]=[l2] вы создаете рекурсивную структуру данных.

Если вы используете l1[len(l1):]=l3 , вы делаете истинную (неглубокую) копию элементов в l3 .

Это довольно фундаментальные идиомы Python, и большую часть времени они «поступают правильно». Однако вы можете получить неожиданные результаты, например:

 >>> m=[[None]*2]*3 >>> m [[None, None], [None, None], [None, None]] >>> m[0][1]=33 >>> m [[None, 33], [None, 33], [None, 33]] # probably not what was intended... >>> m[0] is m[1] is m[2] # same object, that's why they all changed True 

Некоторые новички Python пытаются создать многомерность, выполняя что-то вроде m=[[None]*2]*3 Первая репликация последовательности работает так, как ожидалось; он создает 2 копии None . Это вторая проблема: она создает три копии ссылки на первый список. Таким образом, ввод m[0][1]=33 изменяет список внутри списка, связанного с m, а затем все связанные ссылки меняются, чтобы показать это изменение.

По сравнению с:

 >>> m=[[None]*2,[None]*2,[None]*2] >>> m [[None, None], [None, None], [None, None]] >>> m[0][1]=33 >>> m [[None, 33], [None, None], [None, None]] 

Вы также можете использовать вложенные списки, чтобы сделать то же самое:

 >>> m=[[ None for i in range(2)] for j in range(3)] >>> m [[None, None], [None, None], [None, None]] >>> m[0][1]=44 >>> m [[None, 44], [None, None], [None, None]] >>> m[0] is m[1] is m[2] # three different lists.... False 

Для списков и ссылок Fredrik Lundh имеет этот текст для хорошего вступления.

Что касается ваших конкретных вопросов:

1) В Python все является меткой или ссылкой на объект. Нет «оригинала» (концепция C ++), и нет никакого различия между «ссылкой», указателем или фактическими данными (концепция C / Perl)

2) Фредрик Лунд имеет большую аналогию относительно вопроса, подобного этому :

Точно так же, как вы получаете имя этой кошки, которую вы нашли на своем крыльце: сам кот (объект) не может сказать вам своего имени, и это все равно, поэтому единственный способ узнать, что он назвал, – это спросите всех своих соседей (пространства имен), если это их кошка (объект) …

…. и не удивляйтесь, если вы обнаружите, что это известно по многим именам или вообще нет имени!

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

3) Верно.

1- Является ли изменчивым объектом всегда ссылка? Можете ли вы всегда предположить, что его изменение изменяет оригинал (если вы специально не делаете копию с l2 = l1 [:] вид идиомы)?

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

2 – Могу ли я собрать список всех одинаковых ссылок в Python? т.е. некоторая функция f (l1), которая возвращает ['l1', 'd', 't'], если все они относятся к одному и тому же списку?

Это странно, но это можно сделать.

Вы можете сравнивать объекты для «samenes» с оператором is . Как и в l1 is t[0]

И вы можете получить все связанные объекты с функцией gc.get_referrers в модуле сборщика мусора (gc). Вы можете проверить, какой из этих источников указывает ваш объект с оператором is . Итак, да, это можно сделать. Я просто не думаю, что это будет хорошая идея. Скорее всего, оператор предлагает вам способ сделать то, что вам нужно в одиночку

3- Это мое предположение, что несмотря ни на что, данные будут оставаться действительными до тех пор, пока есть какая-то ссылка на него.

Да.

Является ли изменчивый объект всегда ссылкой? Можете ли вы всегда предположить, что его изменение изменяет оригинал (если вы специально не делаете копию с l2 = l1 [:] вид идиомы)?

Python имеет ссылочную семантику: переменные не хранят значения, как в C ++, а вместо этого маркируют их. Понятие «оригинал» ошибочно: если две переменные обозначают одно и то же значение, совершенно не имеет значения, какой из них «пришел первым». Не имеет значения, изменен ли объект или нет (за исключением того, что неизменяемые объекты не позволят легко понять, что происходит за кулисами). Чтобы сделать копии более универсальным способом, попробуйте модуль copy .

Могу ли я собрать список всех тех же ссылок в Python? т.е. некоторая функция f (l1), которая возвращает ['l1', 'd', 't'], если все они относятся к одному и тому же списку?

Не легко. Подробнее см. В ответе aaronasterling. Вы также можете попробовать что-то вроде k for k, v in locals().items() if v is the_object , но вам также придется искать globals() , вы будете пропускать некоторые вещи, и это может вызвать некоторые проблемы из-за рекурсии с именами «k» и «v» (я не тестировал).

Я полагаю, что независимо от того, что данные будут оставаться действительными, пока есть какая-то ссылка на него.

Абсолютно.

  1. «… объект – это ссылка …» – это нонсенс. Ссылки не являются объектами. Переменные, поля членов, слоты в списках и наборах и т. Д. Содержат ссылки, и эти ссылки указывают на объекты. Может быть любое число (в реализациях без refcouting, даже без них – временно, т.е. до тех пор, пока GC не начнет) ссылается на объект. Каждый, кто имеет ссылку на объект, может ссылаться на его методы, получать доступ к своим членам и т. Д. – это верно для всех объектов. Конечно, только измененные объекты могут быть изменены таким образом, поэтому вам обычно не нужны неизменные.
  2. Да, как показали другие. Но это не обязательно, если вы не отлаживаете GC или не отслеживаете серьезную утечку памяти в своем коде – почему вы думаете, что вам это нужно?
  3. У Python есть автоматическое управление памятью, так что да. Пока есть ссылка на объект, он не будет удален (однако он может остаться в живых на некоторое время после того, как он станет недоступным из-за циклических ссылок и того факта, что GCs запускаются только время от времени).
 1a. Is a mutable object always a reference? 

Нет никакой разницы между изменяемыми и непеременными объектами. Видя имена переменных в качестве ссылок, полезно для людей с C-фоном (но подразумевает, что они могут быть разыменованы, что они не могут).

 1b. Can you always assume that modifying it modifies the original 

Пожалуйста, это не «оригинал». Это тот же объект. b = a означает, что b и a теперь являются одним и тем же объектом.

 1c. (Unless you specifically make a copy with l2=l1[:] kind of idiom)? 

Правильно, потому что тогда это уже не тот самый объект. (Хотя записи n списка будут теми же объектами, что и исходный список).

 2. Can I assemble a list of all the same references in Python? 

Да, возможно, но вам никогда не понадобится, так что это будет пустой тратой энергии. 🙂

 3. It is my assumption that no matter what, the data will remain valid so long as there is some reference to it. 

Да, объект не будет собирать мусор, если у вас есть ссылка на него. (Использование слова «valid» здесь кажется неправильным, но я предполагаю, что это то, что вы имеете в виду).