ИСХОДНЫЙ ВОПРОС:
(Мой вопрос относится к Python 3.2+, но я сомневаюсь, что это изменилось с Python 2.7.)
Предположим, я использую выражение, которое мы обычно ожидаем для создания объекта. Примеры: [1,2,3]
; 42
; 'abc'
; range(10)
; True
; open('readme.txt')
; MyClass()
; lambda x : 2 * x
; и т.п.
Предположим, что два таких выражения выполняются в разное время и "оцениваются до одного и того же значения" (т. е. имеют один и тот же тип и сравниваются как равные). При каких условиях Python предоставляет то, что я называю гарантией отдельного объекта, что два выражения на самом деле создают два разных объекта (т. находятся в области одновременно)?
Я понимаю, что для объектов любого изменяемого типа действует «гарантия отдельного объекта»:
x = [1,2]
y = [1,2]
assert x is not y # guaranteed to pass
Я также знаю, что для некоторых неизменяемых типов (str
, int
) гарантия не действует; а для некоторых других неизменяемых типов (bool
, NoneType
) выполняется противоположная гарантия:
x = True
y = not not x
assert x is not y # guaranteed to fail
x = 2
y = 3 - 1
assert x is not y # implementation-dependent; likely to fail in CPython
x = 1234567890
y = x + 1 - 1
assert x is not y # implementation-dependent; likely to pass in CPython
Но как насчет всех остальных неизменяемых типов?
В частности, могут ли два кортежа, созданные в разное время, иметь одинаковую идентичность?
Причина, по которой меня это интересует, заключается в том, что я представляю узлы в своем графе как кортежи int
, а модель предметной области такова, что любые два узла различны (даже если они представлены кортежами с одинаковыми значениями). Мне нужно создать наборы узлов. Если Python гарантирует, что кортежи, созданные в разное время, являются разными объектами, я мог бы просто создать подкласс tuple
, чтобы переопределить равенство для обозначения идентичности:
class DistinctTuple(tuple):
__hash__ = tuple.__hash__
def __eq__(self, other):
return self is other
x = (1,2)
y = (1,2)
s = set(x,y)
assert len(s) == 1 # pass; but not what I want
x = DistinctTuple(x)
y = DistinctTuple(y)
s = set(x,y)
assert len(s) == 2 # pass; as desired
Но если кортежи, созданные в разное время, не гарантированно отличаются друг от друга, то приведенный выше метод — ужасная техника, которая скрывает дремлющую ошибку, которая может появиться случайным образом и которую может быть очень трудно воспроизвести и найти. В этом случае создание подклассов не поможет; На самом деле мне нужно будет добавить к каждому кортежу в качестве дополнительного элемента уникальный идентификатор. Кроме того, я могу преобразовать свои кортежи в списки. В любом случае, я бы использовал больше памяти. Очевидно, я бы предпочел не использовать эти альтернативы, если только мое исходное решение для создания подклассов небезопасно.
Я предполагаю, что Python не предлагает «гарантию отдельного объекта» для неизменяемых типов, встроенных или определяемых пользователем. Но я не нашел четкого утверждения об этом в документации.
ОБНОВЛЕНИЕ 1:
@LuperRouch @larsmans Спасибо за обсуждение и ответ. Вот последний вопрос, который мне все еще неясен:
Есть ли шанс, что создание объекта пользовательского типа приведет к повторному использованию существующего объекта?
Если это возможно, я хотел бы знать, как я могу проверить для любого класса, с которым я работаю, может ли он демонстрировать такое поведение.
Вот мое понимание. Каждый раз, когда создается объект определяемого пользователем класса, сначала вызывается метод класса __new__()
. Если этот метод переопределить, ничто в языке не помешает программисту вернуть ссылку на существующий объект, тем самым нарушив мою "гарантию отдельного объекта". Очевидно, я могу наблюдать это, изучая определение класса.
Я не уверен, что произойдет, если пользовательский класс не переопределит __new__()
(или явно полагается на __new__()
из базового класса). если я напишу
class MyInt(int):
pass
созданием объекта занимается int.__new__()
. Я ожидаю, что это означает, что иногда я могу увидеть, что следующее утверждение не работает:
x = MyInt(1)
y = MyInt(1)
assert x is not y # may fail, since int.__new__() might return the same object twice?
Но в своих экспериментах с CPython я не смог добиться такого поведения. Означает ли это, что язык предоставляет «гарантию отдельного объекта» для определяемых пользователем классов, которые не переопределяют __new__
, или это просто произвольное поведение реализации?
ОБНОВЛЕНИЕ 2:
Хотя мой DistinctTuple
оказался совершенно безопасной реализацией, теперь я понимаю, что моя дизайнерская идея использовать DistinctTuple
для моделирования узлов очень плоха.
Оператор идентификации уже доступен в языке; заставлять ==
вести себя так же, как is
, логически излишне.
Хуже того, если ==
можно было сделать что-то полезное, я сделал его недоступным. Например, вполне вероятно, что где-то в моей программе я захочу посмотреть, представлены ли два узла одной и той же парой целых чисел; ==
идеально подошёл бы для этого - и на самом деле это то, что он делает по умолчанию...
Что еще хуже, большинство людей на самом деле ожидают, что ==
будет сравнивать некоторое "значение", а не идентичность - даже для определяемого пользователем класса. Они были бы застигнуты врасплох моим переопределением, которое смотрит только на личность.
Наконец... единственная причина, по которой мне пришлось переопределить ==
, заключалась в том, чтобы позволить нескольким узлам с одним и тем же представлением кортежа быть частью набора. Это неправильный подход! Необходимо изменить не ==
поведение, а тип контейнера! Мне просто нужно было использовать мультимножества вместо наборов.
Короче говоря, хотя мой вопрос может иметь некоторую ценность для других ситуаций, я абсолютно убежден, что создание class DistinctTuple
— ужасная идея для моего варианта использования (и я сильно подозреваю, что у него вообще нет подходящего варианта использования).
None
иbool
, перегрузив__new__
, но неизменность объектов не обязательно обеспечивается реализацией. - person Fred Foo   schedule 17.04.2012__new__
, но тогда вы не создаете пользовательские объекты. - person Luper Rouch   schedule 17.04.2012MyTuple
и вернуть объекты из него вMyTuple__new__
. - person Fred Foo   schedule 17.04.2012__new__
гарантирует наличие нового идентификатора для каждого экземпляра (поскольку пользовательские типы изменяемы). - person Luper Rouch   schedule 17.04.2012collections.abc
. Тем не менее, может быть способ реализовать действительно неизменяемые классы, написав модули расширения на C или Cython. - person Fred Foo   schedule 17.04.2012str
,tuple
иint
являются неизменяемыми, в то время как определяемые пользователем типы (реализованные в Python) нет. Иными словами, нет никакого способа видоизменить объектstr
-- по крайней мере, без использования кода Python. - person Ethan Furman   schedule 18.04.2012