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 y
i 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 № 1Czy 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).