Ich möchte in der Lage sein, eine Klasse (in Python) zu erstellen, die nach der Initialisierung __init__
keine neuen Attribute akzeptiert, aber Änderungen an vorhandenen Attributen akzeptiert. Es gibt verschiedene hack-ish Möglichkeiten, wie ich dies sehen kann, zum Beispiel eine __setattr__
Methode wie
def __setattr__(self, attribute, value):
if not attribute in self.__dict__:
print "Cannot set %s" % attribute
else:
self.__dict__[attribute] = value
und dann __dict__
direkt im Inneren bearbeiten __init__
, aber ich habe mich gefragt, ob es einen "richtigen" Weg gibt, dies zu tun?
python
python-3.x
class
oop
python-datamodel
Astrofrog
quelle
quelle
__setattr__
aber das wäre wahrscheinlich hacky.Antworten:
Ich würde nicht
__dict__
direkt verwenden, aber Sie können eine Funktion hinzufügen, um eine Instanz explizit "einzufrieren":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
quelle
hasattr
gedauertgetattr
, bis ich herausgefunden habe, dass dies auf die Tatsache zurückzuführen ist, dass tatsächlich aufgerufen wird, das Ergebnis weggeworfen wird und im Fehlerfall False zurückgegeben wird (siehe diesen Blog) . Es wurde eine Problemumgehung gefunden, indem durch ersetztnot hasattr(self, key)
wurdekey not in dir(self)
. Dies mag langsamer sein, hat aber das Problem für mich gelöst.Wenn jemand daran interessiert ist, dies mit einem Dekorateur zu tun, ist hier eine funktionierende Lösung:
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
Ziemlich einfach zu bedienen:
@froze_it class Foo(object): def __init__(self): self.bar = 10 foo = Foo() foo.bar = 42 foo.foobar = "no way"
Ergebnis:
>>> Class Foo is frozen. Cannot set foobar = no way
quelle
Slots ist der richtige Weg:
Die pythonische Methode besteht darin, Slots zu verwenden, anstatt mit dem herumzuspielen
__setter__
. Während es das Problem lösen kann, gibt es keine Leistungsverbesserung. Die Attribute von Objekten werden in einem Wörterbuch "__dict__
" gespeichert. Aus diesem Grund können Sie Objekten von Klassen, die wir bisher erstellt haben, dynamisch Attribute hinzufügen. Die Verwendung eines Wörterbuchs zur Speicherung von Attributen ist sehr praktisch, kann jedoch Platzverschwendung für Objekte bedeuten, die nur eine geringe Anzahl von Instanzvariablen enthalten.Wenn wir eine Klasse entwerfen, können wir Slots verwenden, um die dynamische Erstellung von Attributen zu verhindern. Um Slots zu definieren, müssen Sie eine Liste mit dem Namen definieren
__slots__
. Die Liste muss alle Attribute enthalten, die Sie verwenden möchten. Wir demonstrieren dies in der folgenden Klasse, in der die Slots-Liste nur den Namen für ein Attribut "val" enthält.class S(object): __slots__ = ['val'] def __init__(self, v): self.val = v x = S(42) print(x.val) x.new = "not possible"
=> Es kann kein Attribut "neu" erstellt werden:
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'
quelle
Eigentlich willst du nicht
__setattr__
, du willst__slots__
. Fügen Sie__slots__ = ('foo', 'bar', 'baz')
dem Klassenkörper hinzu, und Python stellt sicher, dass auf jeder Instanz nur foo, bar und baz vorhanden sind. Aber lesen Sie die Vorbehalte in den Dokumentationslisten!quelle
__slots__
Werken, aber es wird unter anderem die Serialisierung (z. B. Pickle) unterbrechen ... Es ist normalerweise eine schlechte Idee, Slots zu verwenden, um die Attributerstellung zu steuern, anstatt den Speicheraufwand meiner Meinung nach zu verringern ...__slots__
auch die Mehrfachvererbung. Eine Klasse kann nicht von mehr als einer Klasse erben , dass entweder definiert Schlitze oder Hut ein Beispiel - Layout in C - Code definiert ist (wielist
,tuple
oderint
).__slots__
Ihre Gurken kaputt gehen, verwenden Sie ein altes Gurkenprotokoll. Übergeben Sieprotocol=-1
die Pickle-Methoden für das neueste verfügbare Protokoll, nämlich 2 in Python 2 ( eingeführt 2003 ). Das Standardprotokoll und das neueste Protokoll in Python 3 (3 bzw. 4) werden beide verarbeitet__slots__
.Der richtige Weg ist zu überschreiben
__setattr__
. Dafür ist es da.quelle
__init__
? Ist es, sie__dict__
direkt einzuschalten?__setattr__
in__init__
, durchself.__setattr__ = <new-function-that-you-just-defined>
.__xxx__
Methoden nur für die Klasse und nicht für die Instanz nachgeschlagen werden.Ich mag die Lösung, die einen Dekorateur verwendet, sehr, weil es einfach ist, sie für viele Klassen in einem Projekt zu verwenden, mit minimalen Ergänzungen für jede Klasse. Aber es funktioniert nicht gut mit Vererbung. Hier ist meine Version: Sie überschreibt nur die Funktion __setattr__. Wenn das Attribut nicht vorhanden ist und die Aufruferfunktion nicht __init__ ist, wird eine Fehlermeldung ausgegeben.
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
quelle
Was ist damit:
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)
quelle
Hier ist ein Ansatz, den ich mir ausgedacht habe und der kein _frozen-Attribut oder keine Methode benötigt, um () in init einzufrieren.
Während der Initiale füge ich einfach alle Klassenattribute zur Instanz hinzu.
Ich mag das, weil es kein _frozen gibt, freeze () und _frozen auch nicht in der Ausgabe von vars (instance) angezeigt wird.
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
quelle
names=[]
. Dannd.names.append['Fido']
wird'Fido'
in beided.names
und eingefügte.names
. Ich weiß nicht genug über Python, um zu verstehen, warum.pystrict
ist ein installierbarer Pypi-Dekorateur, der von dieser Stackoverflow-Frage inspiriert wurde und mit Klassen zum Einfrieren verwendet werden kann. In der README gibt es ein Beispiel, das zeigt, warum ein solcher Dekorateur benötigt wird, auch wenn in Ihrem Projekt mypy und pylint ausgeführt werden:pip install pystrict
Verwenden Sie dann einfach den @ strict-Dekorator:
from pystrict import strict @strict class Blah def __init__(self): self.attr = 1
quelle
Ich mag das "Frozen" von Jochen Ritzel. Das Unpraktische ist, dass die Variable isfrozen dann beim Drucken einer Klasse angezeigt wird .__ dikt Ich habe dieses Problem auf diese Weise umgangen, indem ich eine Liste autorisierter Attribute (ähnlich wie bei Slots ) erstellt habe:
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
quelle
Das
FrozenClass
von Jochen Ritzel ist cool, aber_frozen()
jedes Mal beim Initialisieren einer Klasse anzurufen ist nicht so cool (und Sie müssen das Risiko eingehen, es zu vergessen). Ich habe eine__init_slots__
Funktion hinzugefügt :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
quelle