Подтвердить что ты не робот

Почему идентификатор класса Python не уникален при вызове быстро?

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

Допустим, у вас есть это в каком-то .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 делает это, когда класс вызывается быстро? Я даже не знал, что это сделал Python, или это ошибка? Если это не ошибка, может кто-нибудь объяснить мне, как это исправить или метод, чтобы он генерировал новый идентификатор каждый раз, когда вызывается метод/класс? Я довольно озадачен тем, как это происходит, потому что если я подожду, все изменится, но не в том случае, если я попытаюсь вызвать один и тот же класс два или более раз.

4b9b3361

Ответ 1

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

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

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

class SomeClass:
    next_id = 0

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

Ответ 2

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

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

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


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

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

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


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

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

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

Ответ 3

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

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

Ответ 4

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

Ответ 5

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

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

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

Ответ 6

Пример, где не сохранено местоположение (и id) памяти:

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

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