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:
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.
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).