Chociaż nigdy tego nie potrzebowałem, uderzyło mnie tylko, że utworzenie niezmiennego obiektu w Pythonie może być nieco trudne. Nie możesz po prostu nadpisać __setattr__
, ponieważ wtedy nie możesz nawet ustawić atrybutów w __init__
. Podklasowanie krotki to sztuczka, która działa:
class Immutable(tuple):
def __new__(cls, a, b):
return tuple.__new__(cls, (a, b))
@property
def a(self):
return self[0]
@property
def b(self):
return self[1]
def __str__(self):
return "<Immutable {0}, {1}>".format(self.a, self.b)
def __setattr__(self, *ignored):
raise NotImplementedError
def __delattr__(self, *ignored):
raise NotImplementedError
Ale wtedy masz dostęp do wszystkiego a
i b
zmiennych przez self[0]
i self[1]
, co jest denerwujące.
Czy jest to możliwe w Pure Python? Jeśli nie, jak mam to zrobić z rozszerzeniem C?
(Odpowiedzi, które działają tylko w Pythonie 3 są dopuszczalne).
Aktualizacja:
Więc instacji krotka jest sposobem, aby to zrobić w Pure Python, który działa dobrze z wyjątkiem dodatkowej możliwości uzyskania dostępu do danych przez [0]
, [1]
itd Tak, aby zakończyć to pytanie wszystkim, co jest brakuje howto to zrobić „prawidłowo”, co w języku C Podejrzewam, że byłoby to całkiem proste, po prostu nie wdrażając żadnego geititem
lub setattribute
, itp. Ale zamiast zrobić to sam, oferuję za to nagrodę, ponieważ jestem leniwy. :)
źródło
.a
i.b
? W końcu wydaje się, że po to istnieją właściwości.NotImplemented
ma służyć tylko jako wartość zwracana w przypadku bogatych porównań. Wartość zwracana dla__setatt__()
jest i tak raczej bezcelowa, ponieważ zazwyczaj w ogóle jej nie widać. Kod jakimmutable.x = 42
po cichu nic nie robi. Zamiast tego powinieneś podbićTypeError
.NotImplementedError
, aleTypeError
to właśnie podnosi krotka, jeśli spróbujesz ją zmodyfikować.Odpowiedzi:
Jeszcze jedno rozwiązanie, o którym właśnie pomyślałem: Najprostszy sposób na uzyskanie takiego samego zachowania, jak w przypadku oryginalnego kodu
Nie rozwiązuje problemu polegającego na tym, że atrybuty można uzyskać za pośrednictwem
[0]
itp., Ale przynajmniej jest znacznie krótszy i zapewnia dodatkową korzyść w postaci zgodności zpickle
icopy
.namedtuple
tworzy typ podobny do tego, który opisałem w tej odpowiedzi , tj. wyprowadzony ztuple
i wykorzystujący__slots__
. Jest dostępny w Pythonie 2.6 lub nowszym.źródło
verbose
parametru donamedtuple
kodu jest łatwe do wygenerowania)) jest pojedynczy interfejs / implementacja anamedtuple
jest lepsza niż dziesiątki nieco innych ręcznie napisanych interfejsów / implementacji, które zrób prawie to samo.namedtuple
s byłby kopiowany przez wartość podczas przekazywania przez funkcje?Najłatwiej to zrobić, używając
__slots__
:Instancje
A
są teraz niezmienne, ponieważ nie możesz ustawić dla nich żadnych atrybutów.Jeśli chcesz, aby instancje klasy zawierały dane, możesz to połączyć z wyprowadzaniem z
tuple
:Edycja : jeśli chcesz pozbyć się indeksowania, możesz zastąpić
__getitem__()
:Zauważ, że nie możesz użyć
operator.itemgetter
dla właściwości w tym przypadku, ponieważ będzie to polegaćPoint.__getitem__()
zamiasttuple.__getitem__()
. Fuerthermore to nie przeszkodzi w użyciutuple.__getitem__(p, 0)
, ale nie mogę sobie wyobrazić, jak powinno to stanowić problem.Nie sądzę, aby „właściwym” sposobem tworzenia niezmiennego obiektu było pisanie rozszerzenia C. Python zwykle polega na tym, że osoby wdrażające biblioteki i użytkownicy biblioteki wyrażają zgodę na to osoby dorosłe i zamiast naprawdę wymuszać interfejs, interfejs powinien być jasno określony w dokumentacji. Dlatego nie rozważam możliwości obejścia problemu, który został pominięty,
__setattr__()
poprzez wywołanieobject.__setattr__()
problemu. Jeśli ktoś to robi, robi to na własne ryzyko.źródło
tuple
tutaj__slots__ = ()
zamiast__slots__ = []
? (Tylko wyjaśnienie)__slots__
nie zostanie to zmienione, prawda? Ma to na celu jednorazowe zidentyfikowanie atrybutów, które można ustawić. Czy w takim przypadku nietuple
wydaje się to raczej naturalnym wyborem?__slots__
nie mogę ustawić żadnych atrybutów. A jeśli mam,__slots__ = ('a', 'b')
to atrybuty a i b są nadal zmienne.__setattr__
więc jest to ulepszenie w stosunku do mojego. +1 :)Możesz użyć Cythona do stworzenia typu rozszerzenia dla Pythona:
Działa zarówno w Pythonie 2.x, jak i 3.
Testy
Jeśli nie masz nic przeciwko obsłudze indeksowania
collections.namedtuple
, preferowane jest sugerowane przez @Sven Marnach :źródło
namedtuple
(a dokładniej typu zwracanego przez funkcjęnamedtuple()
) są niezmienne. Zdecydowanie.namedtuple
przechodzi wszystkie testy (z wyjątkiem obsługi indeksowania). Jaki wymóg przegapiłem?__weakref__
w Pythonie?Innym pomysłem byłoby całkowite zablokowanie
__setattr__
i użycieobject.__setattr__
w konstruktorze:Oczywiście możesz użyć
object.__setattr__(p, "x", 3)
do zmodyfikowaniaPoint
instancjip
, ale Twoja oryginalna implementacja ma ten sam problem (spróbujtuple.__setattr__(i, "x", 42)
naImmutable
instancji).Możesz zastosować tę samą sztuczkę w swojej oryginalnej implementacji: pozbyć się
__getitem__()
i wykorzystaćtuple.__getitem__()
w swoich funkcjach właściwości.źródło
__setattr__
, bo nie chodzi o to, aby być niezawodnym. Chodzi o to, aby było jasne, że nie należy go modyfikować i aby zapobiec modyfikacjom przez pomyłkę.Możesz stworzyć
@immutable
dekorator, który zastąpi__setattr__
i zmieni listę__slots__
na pustą, a następnie ozdobi nią__init__
metodę.Edycja: jak zauważył OP, zmiana
__slots__
atrybutu zapobiega tylko tworzeniu nowych atrybutów , a nie modyfikacji.Edit2: Oto implementacja:
Edycja3: Użycie
__slots__
psuje ten kod, ponieważ jeśli zatrzymuje tworzenie pliku__dict__
. Szukam alternatywy.Edit4: Cóż, to wszystko. To trochę hakerskie, ale działa jak ćwiczenie :-)
źródło
object.__setattr__()
łamie to stackoverflow.com/questions/4828080/…Korzystanie z zamrożonej klasy danych
W przypadku Pythona 3.7+ możesz użyć klasy danych z
frozen=True
opcją , która jest bardzo Pythonowym i łatwym w utrzymaniu sposobem robienia tego, co chcesz.Wyglądałoby to mniej więcej tak:
Ponieważ podpowiedzi typu są wymagane dla pól klas danych, użyłem Any z
typing
modułu .Powody, dla których NIE należy używać nazwy Namedtuple
Przed Pythonem 3.7 często zdarzało się, że namedtuples były używane jako niezmienne obiekty. Może to być trudne na wiele sposobów, jednym z nich jest to, że
__eq__
metoda między nazwanymi krotkami nie uwzględnia klas obiektów. Na przykład:Jak widać, nawet jeśli typy
obj1
iobj2
są różne, nawet jeśli nazwy ich pól są różne,obj1 == obj2
nadal dajeTrue
. Dzieje się tak, ponieważ__eq__
użyta metoda jest metodą krotki, która porównuje tylko wartości pól, biorąc pod uwagę ich pozycje. Może to być ogromne źródło błędów, szczególnie jeśli tworzysz podklasy tych klas.źródło
Myślę, że nie jest to całkowicie możliwe, z wyjątkiem użycia krotki lub nazwanej krotki. Bez względu na wszystko, jeśli zastąpisz
__setattr__()
użytkownika, zawsze możesz go ominąć, dzwoniącobject.__setattr__()
bezpośrednio.__setattr__
Gwarantujemy, że każde rozwiązanie, od którego zależy, nie zadziała.Poniżej znajduje się przybliżenie najbliższego, jakie możesz uzyskać bez użycia jakiejś krotki:
ale zepsuje się, jeśli spróbujesz wystarczająco mocno:
ale użycie przez Svena
namedtuple
jest naprawdę niezmienne.Aktualizacja
Ponieważ pytanie zostało zaktualizowane, aby zapytać, jak zrobić to poprawnie w C, oto moja odpowiedź, jak zrobić to poprawnie w Cythonie:
Po pierwsze
immutable.pyx
:i a,
setup.py
aby go skompilować (używając poleceniasetup.py build_ext --inplace
:Następnie, aby to wypróbować:
źródło
Utworzyłem niezmienne klasy, zastępując
__setattr__
i zezwalając na zestaw, jeśli wywołującym jest__init__
:To jeszcze nie wystarczy, ponieważ pozwala każdemu
___init__
zmienić obiekt, ale masz pomysł.źródło
object.__setattr__()
łamie to stackoverflow.com/questions/4828080/ ...__init__
nie jest zbyt satysfakcjonujący.Oprócz doskonałych innych odpowiedzi lubię dodać metodę dla Pythona 3.4 (a może 3.3). Ta odpowiedź opiera się na kilku wcześniejszych odpowiedziach na to pytanie.
W Pythonie 3.4 można używać właściwości bez funkcji ustawiających do tworzenia elementów klas, których nie można modyfikować. (We wcześniejszych wersjach możliwe było przypisywanie do właściwości bez metody ustawiającej).
Możesz go używać w ten sposób:
który będzie drukowany
"constant"
Ale wezwanie
instance.a=10
spowoduje:Wyjaśnienie: właściwości bez seterów są bardzo nową funkcją Pythona 3.4 (i myślę, że 3.3). Jeśli spróbujesz przypisać do takiej właściwości, zostanie zgłoszony błąd. Używając gniazd, ograniczam zmienne członkowskie do
__A_a
(czyli jest__a
).Problem: Przypisywanie do
_A__a
jest nadal możliwe (instance._A__a=2
). Ale jeśli przypiszesz do zmiennej prywatnej, to twoja wina ...Ta odpowiedź między innymi jednak zniechęca do korzystania z
__slots__
. Preferowane może być użycie innych sposobów zapobiegania tworzeniu atrybutów.źródło
property
jest również dostępny w Pythonie 2 (spójrz na kod w samym pytaniu). Nie tworzy niezmiennego obiektu, spróbuj testów z mojej odpowiedzi np.instance.b = 1
Tworzy nowyb
atrybut.A().b = "foo"
tj. Nie pozwolić na ustawienie nowych atrybutów.Oto eleganckie rozwiązanie:
Dziedzicz z tej klasy, zainicjuj swoje pola w konstruktorze i gotowe.
źródło
Jeśli interesują Cię obiekty zachowujące się, to namedtuple jest prawie Twoim rozwiązaniem.
Jak opisano na końcu dokumentacji namedtuple , możesz wyprowadzić własną klasę z namedtuple; a następnie możesz dodać żądane zachowanie.
Na przykład (kod pobrany bezpośrednio z dokumentacji ):
Spowoduje to:
To podejście działa zarówno dla Pythona 3, jak i Pythona 2.7 (przetestowane również na IronPythonie).
Jedynym minusem jest to, że drzewo dziedziczenia jest trochę dziwne; ale to nie jest coś, czym zwykle się bawisz.
źródło
class Point(typing.NamedTuple):
Klasy dziedziczące z następującej
Immutable
klasy są niezmienne, podobnie jak ich wystąpienia, po__init__
zakończeniu wykonywania ich metody. Ponieważ jest to czysty Python, jak zauważyli inni, nic nie stoi na przeszkodzie, aby ktoś użył mutujących specjalnych metod z bazyobject
itype
, ale to wystarczy, aby uniemożliwić każdemu przypadkową mutację klasy / instancji.Działa poprzez przejmowanie procesu tworzenia klas za pomocą metaklasy.
źródło
Potrzebowałem tego jakiś czas temu i postanowiłem stworzyć dla niego pakiet w Pythonie. Pierwsza wersja jest teraz na PyPI:
Używać:
Pełna dokumentacja tutaj: https://github.com/theengineear/immutable
Mam nadzieję, że to pomoże, otacza nazwane trzy elementy, jak już omówiono, ale znacznie upraszcza tworzenie instancji.
źródło
Ten sposób nie przestaje
object.__setattr__
działać, ale nadal uważam go za przydatny:może być konieczne zastąpienie większej liczby rzeczy (takich jak
__setitem__
) w zależności od przypadku użycia.źródło
getattr
aby móc podać domyślną wartośćfrozen
. To trochę uprościło sprawę. stackoverflow.com/a/22545808/5987__new__
. Wewnątrz__setattr__
wystarczy zamienić warunek naif name != '_frozen' and getattr(self, "_frozen", False)
freeze()
funkcję. Obiekt zostanie wówczas „raz zamrożony”. Wreszcie zamartwianie sięobject.__setattr__
jest głupie, ponieważ „wszyscy jesteśmy tutaj dorośli”.Począwszy od Python 3.7, możesz używać
@dataclass
dekoratora w swojej klasie i będzie on niezmienny jak struktura! Chociaż może, ale nie__hash__()
musi, dodać metodę do twojej klasy. Zacytować:Oto przykład z dokumentów, do których link powyżej:
źródło
frozen
, tj.@dataclass(frozen=True)
, ale zasadniczo blokuje użycie__setattr__
i__delattr__
jak w większości innych odpowiedzi tutaj. Robi to po prostu w sposób zgodny z innymi opcjami klas danych.Możesz nadpisać setattr i nadal używać init do ustawiania zmiennej. Użyłbyś super klasy setattr . tutaj jest kod.
źródło
pass
zamiastraise NotImplementedError
Tę funkcjonalność
attr
zapewnia moduł strony trzeciej .Edycja: python 3.7 zaadoptował ten pomysł do standardowej biblioteki z
@dataclass
.attr
implementuje zamrożone klasy, zastępując je__setattr__
i ma niewielki wpływ na wydajność w każdym czasie tworzenia wystąpienia, zgodnie z dokumentacją.Jeśli masz zwyczaj używania klas jako typów danych,
attr
może to być szczególnie przydatne, ponieważ zajmuje się za Ciebie szablonem (ale nie robi żadnej magii). W szczególności zapisuje dla Ciebie dziewięć metod dunder (__X__) (chyba że wyłączysz którąkolwiek z nich), w tym repr, init, hash i wszystkie funkcje porównawcze.attr
zapewnia również pomocnika dla__slots__
.źródło
Tak więc piszę odpowiednio dla Pythona 3:
I) za pomocą dekoratora klasy danych i ustaw frozen = True. możemy tworzyć niezmienne obiekty w Pythonie.
w tym celu należy zaimportować klasę danych z klas danych lib i ustawić frozen = True
dawny.
z klas danych importuj klasę danych
o / p:
Źródło: https://realpython.com/python-data-classes/
źródło
Alternatywnym podejściem jest utworzenie opakowania, które sprawia, że instancja jest niezmienna.
Jest to przydatne w sytuacjach, w których tylko niektóre wystąpienia muszą być niezmienne (np. Domyślne argumenty wywołań funkcji).
Może być również używany w niezmiennych fabrykach, takich jak:
Chroni również przed
object.__setattr__
innymi sztuczkami, ale nie radzi sobie z nimi ze względu na dynamiczną naturę Pythona.źródło
Użyłem tego samego pomysłu co Alex: meta-klasa i „znacznik inicjalizacji”, ale w połączeniu z nadpisywaniem __setattr__:
Uwaga: wywołuję meta-klasę bezpośrednio, aby działała zarówno w Pythonie 2.x, jak i 3.x.
Działa również ze slotami ...:
... i wielokrotne dziedziczenie:
Należy jednak pamiętać, że zmienne atrybuty pozostają zmienne:
źródło
Jedną rzeczą, która tak naprawdę nie jest tutaj uwzględniona, jest całkowita niezmienność ... nie tylko obiektu nadrzędnego, ale także wszystkich dzieci. krotki / zestawy zamrożone mogą być na przykład niezmienne, ale obiekty, których są częścią, mogą nie być. Oto mała (niekompletna) wersja, która porządnie wymusza niezmienność przez cały czas:
źródło
Możesz po prostu nadpisać setAttr w końcowej instrukcji init. WTEDY możesz konstruować, ale nie zmieniać. Oczywiście nadal można nadpisać obiekt usint. setAttr ale w praktyce większość języków ma jakąś formę refleksji, więc niezmienność jest zawsze nieszczelną abstrakcją. Niezmienność polega bardziej na zapobieganiu przypadkowemu naruszeniu przez klientów umowy obiektu. Używam:
=============================
Oferowane oryginalne rozwiązanie było nieprawidłowe, zostało to zaktualizowane na podstawie komentarzy korzystających z rozwiązania z tego miejsca
Oryginalne rozwiązanie jest w ciekawy sposób błędne, więc jest zawarte na dole.
===============================
Wynik :
======================================
Oryginalna implementacja:
W komentarzach wskazano poprawnie, że to w rzeczywistości nie działa, ponieważ zapobiega tworzeniu więcej niż jednego obiektu, ponieważ nadpisujesz metodę setattr klasy, co oznacza, że drugi nie może zostać utworzony jako self. A = will niepowodzenie przy drugiej inicjalizacji.
źródło
Poniższe podstawowe rozwiązanie dotyczy następującego scenariusza:
__init__()
można zapisać, uzyskując dostęp do atrybutów w zwykły sposób.Chodzi o to, aby przesłonić
__setattr__
metodę i zastąpić jej implementację za każdym razem, gdy zmienia się stan zamrożenia obiektu.Potrzebujemy więc metody (
_freeze
), która przechowuje te dwie implementacje i przełącza się między nimi na żądanie.Ten mechanizm może być zaimplementowany w klasie użytkownika lub dziedziczony ze specjalnej
Freezer
klasy, jak pokazano poniżej:źródło
Prawdziwy
dict
Mam bibliotekę open source, w której robię rzeczy w funkcjonalny sposób, więc przenoszenie danych w niezmiennym obiekcie jest pomocne. Jednak nie chcę przekształcać obiektu danych, aby klient mógł z nimi współdziałać. Tak więc wymyśliłem to - daje ci dykt, taki jak obiekt, który jest niezmienny + kilka metod pomocniczych.
Podziękowania dla Svena Marnacha w jego odpowiedzi za podstawową implementację ograniczania aktualizacji i usuwania nieruchomości.
Metody pomocnicze
Przykłady
źródło