Frage

Ich möchte in der Lage sein, eine Klasse (in Python) zu erstellen, die einmal mit initialisiert wurde __init__, akzeptiert keine neuen Attribute, akzeptiert jedoch Änderungen bestehender Attribute.Ich kann mir vorstellen, dass es dafür mehrere Hacker-Methoden gibt, zum Beispiel eine __setattr__ Methode wie z

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

und dann bearbeiten __dict__ direkt drinnen __init__, aber ich habe mich gefragt, ob es einen „richtigen“ Weg gibt, dies zu tun?

War es hilfreich?

Lösung

Ich würde nicht benutzen __dict__ Direkt, aber Sie können eine Funktion hinzufügen, um eine Instanz explizit zu "einfrieren":

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

Andere Tipps

Wenn jemand daran interessiert ist, dies mit einem Dekorateur zu tun, finden Sie 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

Eigentlich willst du nicht __setattr__, Sie wollen __slots__. Hinzufügen __slots__ = ('foo', 'bar', 'baz') Für den Klassenkörper, und Python wird sicherstellen, dass es in jedem Fall nur Foo, Bar und Baz gibt. Aber lesen Sie die Vorbehalte die Dokumentationslisten!

Slots sind der richtige Weg:

Der pythonische Weg besteht darin, Slots zu verwenden, anstatt damit herumzuspielen __setter__.Das Problem kann zwar gelöst werden, die Leistung wird jedoch nicht verbessert.Die Attribute von Objekten werden in einem Wörterbuch gespeichert.__dict__", das ist der Grund, warum Sie Objekten von Klassen, die wir bisher erstellt haben, dynamisch Attribute hinzufügen können.Die Verwendung eines Wörterbuchs zur Attributspeicherung ist sehr praktisch, kann jedoch eine Platzverschwendung für Objekte bedeuten, die nur über eine geringe Anzahl an Instanzvariablen verfügen.

Schlüssel sind eine gute Möglichkeit, dieses Platzverbrauchsproblem zu umgehen.Anstelle eines dynamischen Diktats, das das dynamische Hinzufügen von Attributen zu Objekten ermöglicht, bieten Slots eine statische Struktur, die das Hinzufügen von Attributen nach der Erstellung einer Instanz verhindert.

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 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'

Hinweis:

  1. Seit Python 3.3 ist der Vorteil der Optimierung des Speicherplatzverbrauchs nicht mehr so ​​beeindruckend.Mit Python 3.3 Schlüsselfreigabe Wörterbücher werden zur Speicherung von Objekten verwendet.Die Attribute der Instanzen sind in der Lage, einen Teil ihres internen Speichers untereinander zu teilen, d. h.der Teil, der die Schlüssel und ihre entsprechenden Hashes speichert.Dies trägt dazu bei, den Speicherverbrauch von Programmen zu reduzieren, die viele Instanzen nicht integrierter Typen erstellen.Dennoch ist es der richtige Weg, dynamisch erstellte Attribute zu vermeiden.

  2. Auch die Nutzung von Slots ist mit eigenen Kosten verbunden.Dadurch wird die Serialisierung unterbrochen (z. B.Essiggurke).Dadurch wird auch die Mehrfachvererbung unterbrochen.Eine Klasse kann nicht von mehr als einer Klasse erben, die entweder Slots definiert oder ein im C-Code definiertes Instanzlayout hat (wie Liste, Tupel oder Int).

Der richtige Weg ist, überschreiben __setattr__. Dafür ist es da.

Ich mag die Lösung, die einen Dekorateur verwendet, da es für viele Klassen in einem Projekt einfach zu verwenden ist, wobei für jede Klasse minimale Ergänzungen verwendet werden. Aber es funktioniert nicht gut mit der Vererbung. Hier ist also meine Version: Sie überschreibt nur die Funktion __setAttr__ - wenn das Attribut nicht vorhanden ist und die Anruferfunktion nicht __init__ ist, druckt es eine Fehlermeldung.

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

Hier ist der Ansatz, den ich gefunden habe, der kein _frozen -Attribut oder keine Methode zum Einfrieren () in Init benötigt.

Während drin Ich füge einfach alle Klassenattribute zur Instanz hinzu.

Ich mag das, weil es keine _frozen, freeze () und _frozen gibt auch nicht in der vars (Instance) Ausgabe.

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

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)

Ich mag das "Frozen" von Jochen Ritzel. Das Unannehmliche ist, dass die Isfrozen Variable erscheint dann beim Drucken einer Klasse .__ DICTIch habe dieses Problem auf diese Weise umgegangen, indem ich eine Liste autorisierter Attribute erstellt habe (ähnlich wie Schlüssel):

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

Das FrozenClass von Jochen Ritzel ist cool, aber anrufen _frozen() Wenn Sie jedes Mal eine Klasse initiieren, ist nicht so cool (und Sie müssen das Risiko eingehen, sie zu vergessen). Ich fügte a hinzu __init_slots__ Funktion:

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

pystrict ist Ein pypi installierbarer Dekorateur Inspiriert von dieser Stackoverflow -Frage, die mit Klassen verwendet werden kann, um sie einzufrieren. Das Readme gibt ein Beispiel, in dem zeigt, warum ein Dekorateur wie diesen auch dann benötigt wird, wenn Sie MyPy und Pylint in Ihrem Projekt laufen:

pip install pystrict

Verwenden Sie dann einfach den @strict Decorator:

from pystrict import strict

@strict
class Blah
  def __init__(self):
     self.attr = 1
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top