Chcę mieć możliwość stworzenia klasy (w Pythonie), która po zainicjowaniu __init__
nie akceptuje nowych atrybutów, ale akceptuje modyfikacje istniejących atrybutów. Jest kilka hakerskich sposobów, aby to zrobić, na przykład mając __setattr__
metodę taką jak
def __setattr__(self, attribute, value):
if not attribute in self.__dict__:
print "Cannot set %s" % attribute
else:
self.__dict__[attribute] = value
a potem edycję __dict__
bezpośrednio w środku __init__
, ale zastanawiałem się, czy istnieje „właściwy” sposób na zrobienie tego?
python
python-3.x
class
oop
python-datamodel
astrofrog
źródło
źródło
__setattr__
ale to prawdopodobnie byłoby hakerskie.Odpowiedzi:
Nie użyłbym
__dict__
bezpośrednio, ale możesz dodać funkcję, aby jawnie „zamrozić” instancję:class FrozenClass(object): __isfrozen = False def __setattr__(self, key, value): if self.__isfrozen and not hasattr(self, key): raise TypeError( "%r is a frozen class" % self ) object.__setattr__(self, key, value) def _freeze(self): self.__isfrozen = True class Test(FrozenClass): def __init__(self): self.x = 42# self.y = 2**3 self._freeze() # no new attributes after this point. a,b = Test(), Test() a.x = 10 b.z = 10 # fails
źródło
hasattr
dzwonigetattr
, odrzuca wynik i zwraca Fałsz w przypadku błędów, patrz ten blog . Znaleziono obejście poprzez zastąpienienot hasattr(self, key)
przezkey not in dir(self)
. To może być wolniejsze, ale rozwiązało problem.Jeśli ktoś jest zainteresowany zrobieniem tego z dekoratorem, oto działające rozwiązanie:
from functools import wraps def froze_it(cls): cls.__frozen = False def frozensetattr(self, key, value): if self.__frozen and not hasattr(self, key): print("Class {} is frozen. Cannot set {} = {}" .format(cls.__name__, key, value)) else: object.__setattr__(self, key, value) def init_decorator(func): @wraps(func) def wrapper(self, *args, **kwargs): func(self, *args, **kwargs) self.__frozen = True return wrapper cls.__setattr__ = frozensetattr cls.__init__ = init_decorator(cls.__init__) return cls
Całkiem proste w użyciu:
@froze_it class Foo(object): def __init__(self): self.bar = 10 foo = Foo() foo.bar = 42 foo.foobar = "no way"
Wynik:
>>> Class Foo is frozen. Cannot set foobar = no way
źródło
Sloty to droga do zrobienia:
Pythonowym sposobem jest używanie automatów zamiast bawić się z
__setter__
. Chociaż może to rozwiązać problem, nie poprawia wydajności. Atrybuty obiektów przechowywane są w "__dict__
" słowniku , dlatego można dynamicznie dodawać atrybuty do obiektów klas, które do tej pory stworzyliśmy. Używanie słownika do przechowywania atrybutów jest bardzo wygodne, ale może oznaczać stratę miejsca dla obiektów, które mają tylko niewielką liczbę zmiennych instancji.Kiedy projektujemy klasę, możemy użyć slotów, aby zapobiec dynamicznemu tworzeniu atrybutów. Aby zdefiniować gniazda, musisz zdefiniować listę z nazwą
__slots__
. Lista musi zawierać wszystkie atrybuty, których chcesz użyć. Pokazujemy to w następującej klasie, w której lista slotów zawiera tylko nazwę atrybutu „val”.class S(object): __slots__ = ['val'] def __init__(self, v): self.val = v x = S(42) print(x.val) x.new = "not possible"
=> Nie udało się utworzyć atrybutu „nowy”:
42 Traceback (most recent call last): File "slots_ex.py", line 12, in <module> x.new = "not possible" AttributeError: 'S' object has no attribute 'new'
źródło
Właściwie nie chcesz
__setattr__
, chcesz__slots__
. Dodaj__slots__ = ('foo', 'bar', 'baz')
do treści klasy, a Python upewni się, że w każdej instancji są tylko foo, bar i baz. Ale przeczytaj zastrzeżenia na listach dokumentacji!źródło
__slots__
działa, ale złamie między innymi serializację (np. Pikle) ... Zwykle złym pomysłem jest używanie slotów do sterowania tworzeniem atrybutów, zamiast zmniejszać narzut pamięci, tak czy inaczej ...__slots__
również przerywa dziedziczenie wielokrotne. Klasa nie może dziedziczyć z więcej niż jednej klasy, albo Definiuje szczeliny lub nakrycie układ przykład te opisane w kodzie C (tak jaklist
,tuple
alboint
).__slots__
łamiesz marynaty, używasz starożytnego protokołu marynowania. Przejdźprotocol=-1
do metod pikle dla najnowszego dostępnego protokołu, czyli 2 w Pythonie 2 ( wprowadzonym w 2003 ). Domyślne i najnowsze protokoły w Pythonie 3 (odpowiednio 3 i 4) obsługują oba__slots__
.Właściwym sposobem jest nadpisanie
__setattr__
. Po to tam jest.źródło
__init__
? Czy jest to ustawienie ich__dict__
bezpośrednio?__setattr__
w__init__
, przezself.__setattr__ = <new-function-that-you-just-defined>
.__xxx__
metody są wyszukiwane tylko w klasie, a nie w instancji.Bardzo podoba mi się rozwiązanie wykorzystujące dekorator, ponieważ jest łatwe w użyciu na wielu zajęciach w projekcie, z minimalnymi dodatkami dla każdej klasy. Ale nie działa dobrze w przypadku dziedziczenia. Oto moja wersja: zastępuje tylko funkcję __setattr__ - jeśli atrybut nie istnieje, a funkcja wywołująca nie jest __init__, wyświetla komunikat o błędzie.
import inspect def froze_it(cls): def frozensetattr(self, key, value): if not hasattr(self, key) and inspect.stack()[1][3] != "__init__": print("Class {} is frozen. Cannot set {} = {}" .format(cls.__name__, key, value)) else: self.__dict__[key] = value cls.__setattr__ = frozensetattr return cls @froze_it class A: def __init__(self): self._a = 0 a = A() a._a = 1 a._b = 2 # error
źródło
A co z tym:
class A(): __allowed_attr=('_x', '_y') def __init__(self,x=0,y=0): self._x=x self._y=y def __setattr__(self,attribute,value): if not attribute in self.__class__.__allowed_attr: raise AttributeError else: super().__setattr__(attribute,value)
źródło
Oto podejście, które wymyśliłem, które nie wymaga atrybutu lub metody _frozen, aby freeze () w init.
Podczas inicjalizacji po prostu dodaję wszystkie atrybuty klasy do instancji.
Podoba mi się to, ponieważ nie ma funkcji _frozen, freeze (), a _frozen również nie pojawia się w danych wyjściowych vars (instancja).
class MetaModel(type): def __setattr__(self, name, value): raise AttributeError("Model classes do not accept arbitrary attributes") class Model(object): __metaclass__ = MetaModel # init will take all CLASS attributes, and add them as SELF/INSTANCE attributes def __init__(self): for k, v in self.__class__.__dict__.iteritems(): if not k.startswith("_"): self.__setattr__(k, v) # setattr, won't allow any attributes to be set on the SELF/INSTANCE that don't already exist def __setattr__(self, name, value): if not hasattr(self, name): raise AttributeError("Model instances do not accept arbitrary attributes") else: object.__setattr__(self, name, value) # Example using class Dog(Model): name = '' kind = 'canine' d, e = Dog(), Dog() print vars(d) print vars(e) e.junk = 'stuff' # fails
źródło
names=[]
. Następnied.names.append['Fido']
wstawi'Fido'
zarównod.names
ie.names
. Nie wiem wystarczająco dużo o Pythonie, aby zrozumieć, dlaczego.pystrict
to instalowalny dekorator pypi zainspirowany tym pytaniem o przepełnienie stosu, którego można używać z klasami, aby je zamrozić. W pliku README znajduje się przykład, który pokazuje, dlaczego dekorator taki jak ten jest potrzebny, nawet jeśli w Twoim projekcie działa mypy i pylint:pip install pystrict
Następnie użyj dekoratora @strict:
from pystrict import strict @strict class Blah def __init__(self): self.attr = 1
źródło
Podoba mi się „Kraina lodu” Jochena Ritzela. Niewygodne jest to, że zmienna isfrozen następnie pojawia się podczas drukowania klasa .__ dict poszedłem wokół tego problemu w ten sposób, tworząc listę autoryzowanych atrybutów (podobnych do szczelin ):
class Frozen(object): __List = [] def __setattr__(self, key, value): setIsOK = False for item in self.__List: if key == item: setIsOK = True if setIsOK == True: object.__setattr__(self, key, value) else: raise TypeError( "%r has no attributes %r" % (self, key) ) class Test(Frozen): _Frozen__List = ["attr1","attr2"] def __init__(self): self.attr1 = 1 self.attr2 = 1
źródło
FrozenClass
Jochen Ritzel jest cool, ale nazywając_frozen()
gdy initialing klasę za każdym razem nie jest tak fajnie (i trzeba podjąć ryzyko zapominając go). Dodałem__init_slots__
funkcję:class FrozenClass(object): __isfrozen = False def _freeze(self): self.__isfrozen = True def __init_slots__(self, slots): for key in slots: object.__setattr__(self, key, None) self._freeze() def __setattr__(self, key, value): if self.__isfrozen and not hasattr(self, key): raise TypeError( "%r is a frozen class" % self ) object.__setattr__(self, key, value) class Test(FrozenClass): def __init__(self): self.__init_slots__(["x", "y"]) self.x = 42# self.y = 2**3 a,b = Test(), Test() a.x = 10 b.z = 10 # fails
źródło