105 Stimmen

Die Erstellung neuer Attribute außerhalb von __init__ verhindern

Ich möchte in der Lage sein, eine Klasse (in Python) zu erstellen, die nach der Initialisierung mit __init__ akzeptiert keine neuen Attribute, sondern nur Änderungen an bestehenden Attributen. Es gibt mehrere Hack-ish Möglichkeiten, die ich sehen kann, um dies zu tun, zum Beispiel mit einer __setattr__ Methode wie z.B.

def __setattr__(self, attribute, value):
    if not attribute in self.__dict__:
        print "Cannot set %s" % attribute
    else:
        self.__dict__[attribute] = value

und dann die Bearbeitung __dict__ direkt im Inneren __init__ aber ich habe mich gefragt, ob es einen "richtigen" Weg gibt, dies zu tun?

97voto

Jochen Ritzel Punkte 99416

Ich würde nicht verwenden __dict__ direkt, 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

57voto

Dhia Punkte 8889

Spielautomaten sind der richtige Weg:

Der pythonische Weg ist die Verwendung von Slots, anstatt mit der __setter__ . Dadurch wird das Problem zwar gelöst, aber die Leistung nicht verbessert. Die Attribute von Objekten werden in einem Wörterbuch gespeichert " __dict__ "Das ist der Grund, warum Sie den Objekten der Klassen, die wir bisher erstellt haben, dynamisch Attribute hinzufügen können. Die Verwendung eines Wörterbuchs für die Speicherung von Attributen ist sehr praktisch, kann aber bei Objekten, die nur eine geringe Anzahl von Instanzvariablen haben, eine Platzverschwendung bedeuten.

Steckplätze sind eine gute Möglichkeit, dieses Problem des Platzverbrauchs zu umgehen. Anstelle eines dynamischen Diktats, das das dynamische Hinzufügen von Attributen zu Objekten erlaubt, bieten Slots eine statische Struktur, die das Hinzufügen von Attributen nach der Erstellung einer Instanz verbietet.

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 __slots__ . Die Liste muss alle Attribute enthalten, die Sie verwenden möchten. Wir demonstrieren dies in der folgenden Klasse, in der die Slotliste 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 gelingt nicht, ein Attribut "neu" zu erstellen:

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'

Anmerkungen:

  1. Seit Python 3.3 ist der Vorteil der Optimierung des Platzverbrauchs nicht mehr so beeindruckend. Mit Python 3.3 Gemeinsame Nutzung von Schlüsseln Wörterbücher werden für die Speicherung von Objekten verwendet. Die Attribute der Instanzen sind in der Lage, einen Teil ihres internen Speichers untereinander aufzuteilen, d. h. den Teil, in dem die Schlüssel und die entsprechenden Hashes gespeichert sind. Dies hilft, den Speicherverbrauch von Programmen zu reduzieren, die viele Instanzen von nicht-builtin Typen erzeugen. Aber es ist immer noch der richtige Weg, um dynamisch erzeugte Attribute zu vermeiden.

  2. Die Verwendung von Steckplätzen ist auch mit Kosten verbunden. Es wird die Serialisierung brechen (z.B. Pickle). Es wird auch die Mehrfachvererbung unterbrechen. Eine Klasse kann nicht von mehr als einer Klasse erben, die entweder Slots definiert oder ein Instanzlayout hat, das in C-Code definiert ist (wie list, tuple oder int).

36voto

Falls jemand daran interessiert ist, dies mit einem Dekorateur zu tun, hier ist 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 in der Anwendung:

@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

21voto

Eigentlich wollen Sie nicht __setattr__ wollen Sie __slots__ . hinzufügen __slots__ = ('foo', 'bar', 'baz') in den Klassenkörper einfügen, und Python wird sicherstellen, dass es in jeder Instanz nur foo, bar und baz gibt. Aber lesen Sie die in der Dokumentation aufgeführten Warnungen!

7voto

Katriel Punkte 115208

Der richtige Weg ist die Überschreibung von __setattr__ . Dafür ist sie da.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X