2013-12-24 3 views
23

Я делаю некоторые вещи в python (используя python 3.3.3), и я наткнулся на то, что меня сбивает с толку, так как мой понятный класс получает новый идентификатор каждый раз, когда он вызывается.Почему идентификатор класса Python не уникален при вызове быстро?

Допустим, у вас есть это в какой-то .py файле:

class someClass: pass 

print(someClass()) 
print(someClass()) 

Вышеприведенные возвращает тот же идентификатор, который сбивает с толку меня, так как я звоню на нее, чтобы она не должна быть такой же, верно? Это как работает python, когда один и тот же класс вызывается дважды подряд или нет? Это дает другой идентификатор, когда я жду несколько секунд, но если я сделаю это так же, как в примере выше, похоже, что это не так, что меня сбивает с толку.

>>> print(someClass());print(someClass()) 
<__main__.someClass object at 0x0000000002D96F98> 
<__main__.someClass object at 0x0000000002D96F98> 

Он возвращает то же самое, но почему? Я также замечаю это с диапазонами, например

for i in range(10): 
    print(someClass()) 

Есть ли какая-то особая причина для выполнения python при быстром вызове класса? Я даже не знал, что питон сделал это, или это может быть ошибка? Если это не ошибка, кто-нибудь может объяснить мне, как исправить это или метод, чтобы он генерировал другой идентификатор каждый раз, когда вызывается метод/класс? Я довольно озадачен тем, как это происходит, потому что, если я буду ждать, это изменится, но нет, если я попытаюсь назвать тот же класс два или более раз.

ответ

25

Объект гарантированно будет уникальным за это время жизни объекта, не на протяжении всего срока службы программы. Два объекта someClass, которые вы создаете, существуют только на время вызова до print - после этого они доступны для сбора мусора (и в CPython немедленно освобождаются). Поскольку их время жизни не перекрывается, для них действительно существует общий идентификатор.

В этом случае он также не восстанавливается из-за сочетания двух деталей реализации CPython: во-первых, он собирает сбор мусора путем подсчета ссылок (с некоторой дополнительной магией, чтобы избежать проблем с круговыми ссылками), а во-вторых, id объект связан со значением базового указателя для переменной (т. е. местоположения ее памяти). Таким образом, первый объект, который был последним выделенным объектом, сразу освобождается - не удивительно, что выделенный объект окажется в одном и том же месте (хотя это потенциально также зависит от деталей того, как интерпретатор был составлен).

Если вы полагаетесь на несколько объектов, имеющих разные id, вы можете оставить их рядом - скажем, в списке, чтобы их время жизни перекрывалось.В противном случае, вы можете реализовать идентификатор класса конкретной, который имеет различные гарантии - например:

class SomeClass: 
    next_id = 0 

    def __init__(self): 
     self.id = SomeClass.nextid 
     SomeClass.nextid += 1 
+5

Хорошее объяснение, но один незначительный каламбур. То, как оно написано, подразумевает, что на самом деле память получает 'free'd, а затем' malloc'd (или какой-то эквивалент), когда действительно он даже не выходит за пределы открытого списка PyObject Python, и почему это происходит так последовательно (в соответствии с вашими хорошо объясненными оговорками), даже на разных платформах или с отладочными маллоками и т. д. – abarnert

+1

Базовый 'объект'' tp_dealloc' вызывает [тип tp_free 'кучи] (http://hg.python.org/cpython/file/c3896275c0f6/Objects/typeobject.c#l2370), который является [' PyObject_GC_Del'] (http://hg.python.org/cpython/file/c3896275c0f6/Modules/gcmodule.c#l1621). Это, в свою очередь, использует макрос 'PyObject_FREE'. Предостережение относительно того, как скомпилировано CPython, заключается в том, что [без pymalloc] (http://hg.python.org/cpython/file/c3896275c0f6/Include/objimpl.h#l133) макрос 'PyObject_FREE' определяется как' PyMem_FREE ', который для не-отладочной сборки просто« свободен ». Поэтому в этот момент повторное использование адреса зависит от платформы 'malloc'. – eryksun

+0

Хорошо сказано об упоминании коллекции мусора :). – ivanleoncz

3

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

+0

О, я вижу, есть ли способ сказать python, что память изменилась, так что она создает экземпляр по-разному? Я не уверен, как быстро поменять память, чтобы каждый раз присваивать разные идентификаторы. – user3130555

+0

Я бы не использовал идентификатор в качестве вашего идентификатора. Либо передайте и сохраните переменную счетчика, либо если вы хотите использовать id, добавьте экземпляр в список или другой объект, чтобы он не использовался повторно. – mhlester

+2

Я не знаю, почему вам нужно иметь разные идентификаторы, но, какова бы ни была ваша причина, это, вероятно, неправильно. Также вы должны учитывать, что из-за внутреннего «кэширования» может произойти (с неизменяемыми типами) для двух разных и, по-видимому, несвязанных переменных, использовать один и тот же объект (и id). – smeso

3

Попробуйте это, попробуйте позвонить следующее:

a = someClass() 
for i in range(0,44): 
    print(someClass()) 
print(a) 

Вы увидите что-то другое. Зачем? Причина того, что память, выпущенная первым объектом в цикле «foo», была повторно использована. С другой стороны, a не используется повторно, так как он сохраняется.

13

Если вы читаете документацию для id, он говорит:

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

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


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

  • Сборщик мусора должен очистить первый объект, прежде чем ваш код даже начинает выделять второй объект-который гарантированно произойдет с CPython или любая другая реализация ref-counting (когда нет круговых ссылок), но довольно маловероятна с коллективным сборщиком мусора, как в Jython или IronPython.

  • Распределитель под крышками должен иметь очень сильное предпочтение для повторного использования недавно освобожденных объектов того же типа. Это справедливо в CPython, который имеет несколько уровней фантастических распределителей поверх базового C malloc, но большинство других реализаций оставляют намного больше для базовой виртуальной машины.


последнее: Тот факт, что object.__repr__ случается содержать подстроку, что происходит, чтобы быть таким же, как id как шестнадцатеричное число только реализация артефакт CPython, не гарантируется в любом месте , По the docs:

Если это вообще возможно, это должно выглядеть как допустимое выражение Python, который может быть использован для воссоздания объекта с тем же значением (с учетом соответствующих условий). Если это невозможно, должна быть возвращена строка формы <...some useful description…>.

Тот факт, что CPython-х object происходит поставить hex(id(self)) (на самом деле, я считаю, что это делает эквивалент sprintf -ную свой указатель через %p, но так как CPython-х id просто возвращает тот же самый указатель приведён к long, который заканчивается тем, то же самое) нигде не гарантируется. Даже если это было верно, так как ... до object даже существовало в начале 2.x дней. Вы можете рассчитывать на это для такой простой «что здесь происходит» отладки в интерактивной подсказке, но не пытайтесь использовать ее за ее пределами.

4

Здесь я чувствую более глубокую проблему. Вы не должны полагаться на id для отслеживания уникальных экземпляров на протяжении всей вашей программы. Вы должны просто увидеть его как не гарантированный индикатор местоположения памяти в течение всего экземпляра объекта. Если вы сразу создадите и освободите экземпляры, вы можете очень хорошо создать последовательные экземпляры в том же месте памяти.

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

+0

Я не думаю, что OP пытается использовать здесь 'id' (или, фактически, эквивалентное число, которое появляется в' repr') для каких-либо целей, кроме отладки жизненных сроков объекта ... это одно, для чего это хорошо. – abarnert

+0

@abarnert, если вы видите комментарий OP в ответе mhlester, похоже, что OP фактически ищет такое эквивалентное поведение. –

+0

Хотя из его последующего комментария к тому же ответу, похоже, что он не ищет этого, он просто запутался во время отладки ... – abarnert

0

Пример из где ячейки памяти (и идентификатор) не отпущена:

print([someClass() for i in range(10)]) 

Теперь идентификаторы являются уникальными.

Смежные вопросы