/ / Jakie przedmioty mają inną tożsamość? - python, object, python-3.x, object-identity

Jakie przedmioty mają inną tożsamość? - python, object, python-3.x, object-identity

ORYGINALNE PYTANIE:

(Moje pytanie dotyczy Python 3.2+, ale wątpię, aby zmieniło się to od czasu Pythona 2.7).

Załóżmy, że używam wyrażenia, które zwykle oczekujemy, aby utworzyć obiekt. Przykłady: [1,2,3]; 42; "abc"; range(10); True; open("readme.txt"); MyClass(); lambda x : 2 * x; itp.

Załóżmy, że dwa takie wyrażenia są wykonywane w różnym czasie i "oceniają na tę samą wartość" (tj. Mają ten sam typ i są porównywane jako równe). Na jakich warunkach Python zapewnia to, co nazywam a odrębna gwarancja na przedmiot że dwa wyrażenia faktycznie tworzą dwa różne obiekty (tj. x is y ocenia jako False, zakładając, że dwa obiekty są powiązane x i yi oba są w tym samym czasie)?

Rozumiem, że w przypadku obiektów o dowolnym zmiennym typie "gwarancja odrębnego przedmiotu" zawiera:

x = [1,2]
y = [1,2]
assert x is not y # guaranteed to pass

Wiem też o pewnych niezmiennych typach (str, int) gwarancja nie obowiązuje; i dla niektórych innych niezmiennych typów (bool, NoneType), przeciwna gwarancja zawiera:

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

Ale co z pozostałymi niezmiennymi typami?

W szczególności, czy dwie krotki utworzone w różnym czasie mogą mieć tę samą tożsamość?

Powodem, dla którego jestem tym zainteresowany, jest to, że reprezentuję węzły na moim wykresie jako krotki int, a model domeny jest taki, że dowolne dwa węzłysą różne (nawet jeśli są reprezentowane przez krotki z tymi samymi wartościami). Potrzebuję tworzyć zestawy węzłów. Jeśli Python gwarantuje, że krotki utworzone w różnych czasach są odrębnymi obiektami, mogłabym po prostu podklasować tuple aby na nowo zdefiniować równość jako tożsamość:

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

Ale jeśli krotki utworzone w różnym czasie nie sąGwarantowana odrębność, powyższe jest straszną techniką, która ukrywa uśpiony błąd, który może pojawić się przypadkowo i może być bardzo trudny do skopiowania i znalezienia. W takim przypadku, podklasy wygrywają "t help", tak naprawdę będę musiał dodać do każdej krotki, jako dodatkowy element, unikalny identyfikator. Alternatywnie mogę przekonwertować swoje krotki na listy. Tak czy inaczej, użyję więcej pamięci. Oczywiście, wolałbym nie używać tych alternatyw, chyba że moje oryginalne podklasy nie są bezpieczne.

Domyślam się, że Python nie oferuje "wyraźnej gwarancji przedmiotu" dla niezmiennych typów, zarówno wbudowanych, jak i zdefiniowanych przez użytkownika. Ale nie znalazłem wyraźnego oświadczenia na ten temat w dokumentacji.

UPDATE 1:

@LuperRouch @ larsmans Dziękuję za dyskusję i odpowiedź do tej pory. Oto ostatni problem, z którym wciąż nie jestem pewien:

Czy istnieje szansa na utworzenie obiektu zdefiniowanego przez użytkownika? type powoduje ponowne wykorzystanie istniejącego obiektu?

Jeśli jest to możliwe, chciałbym wiedzieć, w jaki sposób mogę zweryfikować dla każdej klasy, z którą pracuję, czy może ona wykazywać takie zachowanie.

Oto moje zrozumienie Za każdym razem, gdy tworzony jest obiekt klasy zdefiniowanej przez użytkownika, klasa " __new__() metoda nazywa się pierwsza. Jeśli ta metoda zostanie nadpisana, nic w tym języku nie przeszkodzi programistowi w zwrocie odwołania do istniejącego obiektu, naruszając w ten sposób moją "gwarancję odrębnego obiektu". Oczywiście mogę to obserwować, analizując definicję klasy.

Nie jestem pewien, co się stanie, jeśli klasa zdefiniowana przez użytkownika nie zastąpi __new__() (lub wyraźnie polega __new__() od klasy bazowej). Jeśli piszę

class MyInt(int):
pass

tworzenie obiektu jest obsługiwane przez int.__new__(). Spodziewam się, że oznacza to, że czasami może się zdarzyć, że poniższe stwierdzenie się nie powiedzie:

x = MyInt(1)
y = MyInt(1)
assert x is not y # may fail, since int.__new__() might return the same object twice?

Ale w moich eksperymentach z CPythonem nie mogłem osiągnąć takiego zachowania. Czy to oznacza, że ​​język zapewnia "odrębną gwarancję obiektu" dla klas zdefiniowanych przez użytkownika, które nie zastępują __new__czy jest to tylko arbitralne zachowanie wdrożeniowe?

UPDATE 2:

Podczas gdy mój DistinctTuple Okazało się, że jest to całkowicie bezpieczna implementacja, teraz rozumiem, że mój pomysł na projekt użycia DistinctTuple modelowanie węzłów jest bardzo złe.

Operator tożsamości jest już dostępny w języku; zrobienie == zachowywać się w taki sam sposób jak is jest logicznie niepotrzebny.

Gorzej, jeśli == można było zrobić coś pożytecznego, uczyniłem to niedostępnym. Na przykład, jest całkiem prawdopodobne, że gdzieś w moim programie będę chciał zobaczyć, czy dwa węzły są reprezentowane przez tę samą parę liczb całkowitych; == byłby idealny do tego - i tak naprawdę to robi to domyślnie ...

Co gorsza, większość ludzi faktycznie tego oczekuje == porównać nieco "wartość", a nie tożsamość - nawet dla klasy zdefiniowanej przez użytkownika. Zostaną zaskoczeni moim przesłuchem, który patrzy tylko na tożsamość.

W końcu ... jedyny powód, dla którego musiałem przedefiniować == polegało na umożliwieniu wielu węzłów o tej samej reprezentacji krotki jako części zestawu. To jest zły sposób, aby to osiągnąć! To nie jest == zachowanie, które musi się zmienić, jest to typ kontenera! Po prostu potrzebowałem użyć multisetów zamiast zestawów.

Krótko mówiąc, chociaż moje pytanie może mieć jakąś wartość dla innych sytuacji, jestem absolutnie przekonany, że tworzenie class DistinctTuple to okropny pomysł na mój przypadek użycia (i mocno podejrzewam, że w ogóle nie ma żadnego ważnego przypadku użycia).

Odpowiedzi:

3 dla odpowiedzi № 1

Czy jest szansa, że ​​utworzenie obiektu typu zdefiniowanego przez użytkownika spowoduje ponowne wykorzystanie istniejącego obiektu?

Stanie się tak tylko wtedy, gdy typ zdefiniowany przez użytkownika jest jawnie zaprojektowany do tego. Z __new__() lub jakiś metaclass.

Chciałbym wiedzieć, w jaki sposób mogę zweryfikować dla każdej klasy, z którą pracuję, czy może ona wykazywać takie zachowanie.

Użyj źródła, Luke.

Jeśli chodzi o int, małe liczby całkowite są wstępnie przydzielane, a te wstępnie przydzielone liczby całkowite są używane wszędzie tam, gdzie tworzy się obliczenia z liczbami całkowitymi. Nie możesz tego zrobić, kiedy to robisz MyInt(1) is MyInt(1), ponieważ to, co tam masz, nie jest liczbami całkowitymi. Jednak:

>>> MyInt(1) + MyInt(1) is 2
True

Dzieje się tak dlatego, że MyInt (1) + MyInt (1) nie zwraca MyInt. Zwraca int, ponieważ tak właśnie jest __add__ liczby całkowitej zwraca (i tam, gdzie występuje również sprawdzanie dla wstępnie przydzielonych liczb całkowitych) .Jeśli cokolwiek tylko pokazuje, że generowanie podklasy int w ogóle nie jest szczególnie użyteczne. :-)

Czy to oznacza, że ​​język zapewnia "odrębną gwarancję obiektu" dla klas zdefiniowanych przez użytkownika, które nie zastępują Nowyczy jest to tylko arbitralne zachowanie wdrożeniowe?

Nie gwarantuje tego, ponieważ nie ma takiej potrzeby, domyślnym zachowaniem jest utworzenie nowego obiektu, musisz go przesłonić, jeśli nie chcesz, aby tak się stało. Posiadanie gwarancji nie ma sensu.


4 dla odpowiedzi nr 2

Odwołanie Pythona, sekcja 3, Model danych:

dla typów niezmiennych operacje wymuszające nowe wartości może faktycznie zwraca referencję do dowolnego istniejącego obiektu o tym samym typie i wartości, podczas gdy dla obiektów zmiennych jest to nie dozwolony.

(Podkreślenie dodane.)

W praktyce wydaje się, że CPython buforuje tylko pustą krotkę:

>>> 1 is 1
True
>>> (1,) is (1,)
False
>>> () is ()
True

1 dla odpowiedzi nr 3

Jeśli Python gwarantuje, że krotki utworzone w różnych czasach są odrębnymi obiektami, mogłabym po prostu podklasować tuple aby na nowo zdefiniować równość, by oznaczać tożsamość.

Wydajesz się być zdezorientowany, jak działa podklasowanie: Jeśli B podklasy A, następnie B używa wszystkich A"s metody [1] - ale A metody będą pracować nad instancjami B, nie z A. Dotyczy to nawet __new__:

--> class Node(tuple):
...   def __new__(cls):
...     obj = tuple.__new__(cls)
...     print(type(obj))
...     return obj
...
--> n = Node()
<class "__main__.Node">

Jak podkreślił @larsman w Python reference:

w przypadku typów niezmiennych operacje obliczające nowe wartości mogą w rzeczywistości zwrócić odniesienie do dowolnego istniejącego obiektu o tym samym typie i wartości, podczas gdy dla obiektów zmiennych jest to niedozwolone

Należy jednak pamiętać, że ten fragment mówi o Pythonie wbudowany typy, a nie typy zdefiniowane przez użytkownika (które mogą zwariować w dowolny sposób).


Rozumiem powyższy fragment, aby to zagwarantowaćPython nie zwróci nowego zmiennego obiektu, który jest taki sam jak istniejący obiekt, a klasy zdefiniowane przez użytkownika i utworzone w kodzie Pythona są z natury zmienne (ponownie, patrz uwaga powyżej na temat zwariowanych klas zdefiniowanych przez użytkownika).

Bardziej kompletna klasa Node (zauważ, że nie musisz jawnie się odwoływać) tuple.__hash__):

class Node(tuple):
__slots__ = tuple()
__hash__ = tuple.__hash__
def __eq__(self, other):
return self is other
def __ne__(self, other):
return self is not other

--> n1 = Node()
--> n2 = Node()
--> n1 is n2
False
--> n1 == n2
False
--> n1 != n2
True

--> n1 <= n2
True
--> n1 < n2
False

Jak widać z ostatnich dwóch porównań, możesz również zastąpić __le__ i __ge__ metody.

[1] Jedyny wyjątek, którego jestem świadomy, jest __hash__ -- Jeśli __eq__ jest zdefiniowany na podklasie, ale podklasa chce klasy nadrzędnej " __hash__ musi to wyraźnie powiedzieć (jest to zmiana Pythona 3).