Question

Je veux être en mesure de créer une classe (en Python) qu'une fois initialisé avec __init__, ne pas accepter de nouveaux attributs, mais accepte les modifications des attributs existants. Il y a plusieurs façons de hack-ish je peux voir pour ce faire, par exemple ayant une méthode de __setattr__ tels que

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

puis édition __dict__ directement à l'intérieur __init__, mais je me demandais s'il y a une façon de le faire? « Bon »

Était-ce utile?

La solution

Je ne voudrais pas utiliser __dict__ directement, mais vous pouvez ajouter une fonction explicitement « geler » une instance:

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

Autres conseils

Si quelqu'un est intéressé à le faire avec un décorateur, voici une solution de travail:

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

Assez simple à utiliser:

@froze_it 
class Foo(object):
    def __init__(self):
        self.bar = 10

foo = Foo()
foo.bar = 42
foo.foobar = "no way"

Résultat:

>>> Class Foo is frozen. Cannot set foobar = no way

En fait, vous ne voulez pas __setattr__, vous voulez __slots__ . Ajouter __slots__ = ('foo', 'bar', 'baz') au corps de la classe, et Python fera en sorte qu'il n'y a qu'une foo, bar et baz sur toute instance. Mais lisez les mises en garde les listes de documentation!

Machines à sous est le chemin à parcourir:

La façon pythonique est d'utiliser des fentes au lieu de jouer avec le __setter__. Bien qu'il puisse résout le problème, il ne donne aucune amélioration de la performance. Les attributs des objets sont stockés dans un dictionnaire « __dict__ », c'est la raison, pourquoi vous pouvez dynamiquement ajouter des attributs à des objets de classes que nous avons créés. L'utilisation d'un dictionnaire pour le stockage d'attribut est très pratique, mais cela peut signifier un gaspillage d'espace pour les objets qui ont seulement une petite quantité de variables d'instance.

  

Machines à sous sont une belle façon de contourner ce problème de consommation d'espace. Au lieu d'avoir un dict dynamique qui permet d'ajouter des attributs à des objets de manière dynamique, les fentes fournissent une structure statique qui interdit additions après la création d'une instance.

Lorsque nous concevons une classe, nous pouvons utiliser des fentes pour empêcher la création dynamique d'attributs. Pour définir machines à sous, vous devez définir une liste avec le nom __slots__. La liste doit contenir tous les attributs, vous souhaitez utiliser. Nous démontrons dans la classe suivante, dans laquelle la liste des emplacements ne contient que le nom d'un attribut « val ».

class S(object):

    __slots__ = ['val']

    def __init__(self, v):
        self.val = v


x = S(42)
print(x.val)

x.new = "not possible"

=> Il ne parvient pas à créer un attribut "nouveau":

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'
  

NB:

     
      
  1. Depuis Python 3.3 l'avantage d'optimiser la consommation d'espace n'est pas aussi impressionnant plus. Avec Python 3.3 partage Key Les dictionnaires sont utilisés pour le stockage d'objets. Les attributs des instances sont capables de partager une partie de leur stockage interne entre eux, à savoir la partie qui stocke les clés et leurs hash correspondantes. Cela aide à réduire la consommation de mémoire des programmes, qui créent de nombreux cas de non-types BUILTIN. Mais est encore le chemin à parcourir pour éviter les attributs dynamiquement créés.

  2.   
  3. En utilisant les fentes viennent aussi avec son propre coût. Il brisera sérialisation (par exemple cornichon). Il brisera aussi l'héritage multiple. Une classe ne peut hériter de plus d'une classe qui définit une ou l'autre des fentes ou chapeau d'une mise en page de l'instance définies dans le code C (comme liste, tuple ou int).

  4.   

La manière appropriée consiste à substituer __setattr__. C'est ce qu'il est là pour cela.

J'aime beaucoup la solution qui utilise un décorateur, car il est facile de l'utiliser pour de nombreuses classes à travers un projet, avec des additions minimales pour chaque catégorie. Mais il ne fonctionne pas avec l'héritage. Donc, voici ma version. Elle remplace uniquement la fonction __setattr__ - si l'attribut n'existe pas et la fonction de l'appelant est __init__ pas, il affiche un message d'erreur

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

Voici l'approche que je suis venu avec qui n'a pas besoin d'un attribut ou une méthode _frozen de geler () dans initialisation.

Au cours de init j'ajouter tous les attributs de la classe à l'instance.

J'aime parce qu'il n'y a pas _frozen, gel (), et _frozen ne montre pas non plus dans la sortie vars (par exemple).

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

Qu'en est-il ceci:

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)

J'aime le "Frozen" de Jochen Ritzel. L'inconvénient est que le variables IsFrozen apparaît alors lors de l'impression d'une classe .__ dict Je suis allé contourner ce problème en créant ainsi une liste d'attributs autorisés (similaire à emplacements ):

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

Le FrozenClass par Jochen Ritzel est cool, mais appeler _frozen() quand paraphant une classe à chaque fois est pas si cool (et vous devez prendre le risque de l'oublier). J'ai ajouté une fonction __init_slots__:

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 est href="https://github.com/earonesty/pystrict" rel="nofollow noreferrer"> un décorateur pypi installable inspiré par cette question stackoverflow qui peut être utilisé avec des classes de geler leur. Il est un exemple pour le README qui montre pourquoi un décorateur comme celui-ci est nécessaire, même si vous avez mypy et pylint en cours d'exécution sur votre projet:

pip install pystrict

Ensuite, il suffit d'utiliser le décorateur @strict:

from pystrict import strict

@strict
class Blah
  def __init__(self):
     self.attr = 1
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top