def __init__ python python-3.x class oop python-datamodel

def__init__ python



Impedir la creación de nuevos atributos fuera de__init__ (11)

¿Qué tal esto?

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)

Quiero poder crear una clase (en Python) que una vez inicializada con __init__ , no acepte nuevos atributos, pero acepta modificaciones de los atributos existentes. Hay varias formas de hack-ish que puedo ver para hacer esto, por ejemplo tener un método __setattr__ como

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

y luego __dict__ directamente dentro de __init__ , pero me preguntaba si existe una forma "adecuada" de hacerlo.


En realidad, no quieres __setattr__ , quieres __slots__ . Agregue __slots__ = (''foo'', ''bar'', ''baz'') al cuerpo de la clase, y Python se asegurará de que solo haya foo, bar y baz en cualquier instancia. ¡Pero lea las advertencias de las listas de documentación!


Este es el enfoque que se me ocurrió que no necesita un atributo o método _frozen para congelar () en init.

Durante init solo agrego todos los atributos de clase a la instancia.

Me gusta porque no hay _frozen, freeze () y _frozen tampoco aparecen en el resultado de vars (instancia).

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


La FrozenClass de Jochen Ritzel es genial, pero llamar a _frozen() al inicializar una clase no es tan genial (y debes correr el riesgo de olvidarlo). __init_slots__ una función __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


La forma correcta es anular __setattr__ . Para eso está ahí.


Me gusta el "Frozen" de Jochen Ritzel. El inconveniente es que la variable isfrozen aparece cuando imprimo un Class .__ dict He solucionado este problema de esta manera creando una lista de atributos autorizados (similar a los slots ):

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


Me gusta mucho la solución que utiliza un decorador, porque es fácil de usar para muchas clases en un proyecto, con adiciones mínimas para cada clase. Pero no funciona bien con la herencia. Así que aquí está mi versión: solo anula la función __setattr__ - si el atributo no existe y la función de la persona que llama no es __init__, imprime un mensaje de error.

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


No usaría __dict__ directamente, pero puede agregar una función para "congelar" explícitamente una instancia:

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


Si alguien está interesado en hacer eso con un decorador, aquí hay una solución de trabajo:

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

Bastante sencillo de usar:

@froze_it class Foo(object): def __init__(self): self.bar = 10 foo = Foo() foo.bar = 42 foo.foobar = "no way"

Resultado:

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


una mejora sobre esta excelente solución usando __slots__ sería dejar que el método __init__ defina los atributos permitidos, y luego establecer el atributo __slots__ con la clase actual __dict__

La ventaja es que evita la doble actualización al agregar un atributo en __init__ :

class A: def __init__(self): self.a = 12 self.b = 34 self.__slots__ = self.__dict__ a = A() a.b = 33 # okay a.c = 22 # AttributeError: ''A'' object has no attribute ''c''


Slots es el camino a seguir:

La forma pitónica es usar ranuras en lugar de jugar con el __setter__ . Si bien puede resolver el problema, no mejora el rendimiento. Los atributos de los objetos se almacenan en un diccionario " __dict__ ", esta es la razón por la cual puede agregar atributos de forma dinámica a los objetos de las clases que hemos creado hasta ahora. Usar un diccionario para el almacenamiento de atributos es muy conveniente, pero puede significar un desperdicio de espacio para los objetos, que tienen solo una pequeña cantidad de variables de instancia.

Las máquinas tragamonedas son una buena forma de evitar este problema de consumo de espacio. En lugar de tener un dict dinámico que permite agregar atributos a los objetos dinámicamente, los slots proporcionan una estructura estática que prohíbe las adiciones después de la creación de una instancia.

Cuando diseñamos una clase, podemos usar espacios para evitar la creación dinámica de atributos. Para definir ranuras, debe definir una lista con el nombre __slots__ . La lista debe contener todos los atributos que desea usar. Demostramos esto en la siguiente clase, en la que la lista de ranuras contiene solo el nombre de un atributo "val".

class S(object): __slots__ = [''val''] def __init__(self, v): self.val = v x = S(42) print(x.val) x.new = "not possible"

=> No puede crear un atributo "nuevo":

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

NÓTESE BIEN:

  1. Desde Python 3.3, la ventaja de optimizar el consumo de espacio ya no es tan impresionante. Con Python 3.3 Key-Sharing diccionarios Key-Sharing se utilizan para el almacenamiento de objetos. Los atributos de las instancias pueden compartir parte de su almacenamiento interno entre ellos, es decir, la parte que almacena las claves y sus hashes correspondientes. Esto ayuda a reducir el consumo de memoria de los programas, que crean muchas instancias de tipos no integrados. Pero todavía es el camino a seguir para evitar los atributos creados dinámicamente.

  2. El uso de máquinas tragamonedas también tiene su costo. Se romperá la serialización (por ejemplo, salmuera). También romperá la herencia múltiple. Una clase no puede heredar de más de una clase que define ranuras o un diseño de instancia definido en código C (como lista, tupla o int).