Quiero poder crear una clase (en Python) que una vez inicializada con __init__
, no acepta nuevos atributos, pero acepta modificaciones de atributos existentes. Hay varias formas pirateadas que puedo ver para hacer esto, por ejemplo, tener un __setattr__
método como
def __setattr__(self, attribute, value):
if not attribute in self.__dict__:
print "Cannot set %s" % attribute
else:
self.__dict__[attribute] = value
y luego editar __dict__
directamente dentro __init__
, pero me preguntaba si hay una forma 'adecuada' de hacer esto.
python
python-3.x
class
oop
python-datamodel
astrofrog
fuente
fuente
__setattr__
pero probablemente sería un truco.Respuestas:
No lo 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
fuente
hasattr
llamagetattr
, descarta el resultado y devuelve False en caso de errores, consulte este blog . Encontré una solución reemplazandonot hasattr(self, key)
porkey not in dir(self)
. Esto podría ser más lento, pero resolvió el problema por mí.Si alguien está interesado en hacer eso con un decorador, aquí hay una solución funcional:
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
fuente
Slots es el camino a seguir:
La forma pitónica es usar tragamonedas 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 que se pueden agregar atributos dinámicamente 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 una pérdida de espacio para los objetos, que tienen solo una pequeña cantidad de variables de instancia.Cuando diseñamos una clase, podemos usar slots para evitar la creación dinámica de atributos. Para definir ranuras, debes definir una lista con el nombre
__slots__
. La lista debe contener todos los atributos que desee utilizar. 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 crea 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'
fuente
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 en las listas de documentación!fuente
__slots__
funciona, pero romperá la serialización (por ejemplo, pickle), entre otras cosas ... Suele ser una mala idea usar ranuras para controlar la creación de atributos, en lugar de reducir la sobrecarga de memoria, en mi opinión, de todos modos ...__slots__
también rompe la herencia múltiple. Una clase no puede heredar de más de una clase que define ranuras o tiene un diseño de instancia definido en código C (comolist
,tuple
oint
).__slots__
rompe sus pepinillos, está utilizando un protocolo antiguo de pepinillos. Paseprotocol=-1
a los métodos pickle para el protocolo más reciente disponible, que es 2 en Python 2 ( introducido en 2003 ). Los protocolos predeterminados y más recientes de Python 3 (3 y 4 respectivamente) manejan__slots__
.La forma correcta es anular
__setattr__
. Para eso está ahí.fuente
__init__
? ¿Es colocarlos__dict__
directamente?__setattr__
en__init__
, porself.__setattr__ = <new-function-that-you-just-defined>
.__xxx__
métodos solo se buscan en la clase, no en la instancia.Me gusta mucho la solución que usa 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 llamada 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
fuente
¿Qué pasa con 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)
fuente
Aquí está el enfoque que se me ocurrió que no necesita un atributo o método _frozen para congelar () en init.
Durante el inicio, solo agrego todos los atributos de clase a la instancia.
Me gusta esto porque no hay _frozen, freeze (), y _frozen tampoco aparece en la salida 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
fuente
names=[]
. Luegod.names.append['Fido']
insertará'Fido'
en ambosd.names
ye.names
. No sé lo suficiente sobre Python para entender por qué.pystrict
es un decorador instalable de pypi inspirado en esta pregunta de stackoverflow que se puede usar con clases para congelarlas. Hay un ejemplo en README que muestra por qué se necesita un decorador como este incluso si tiene mypy y pylint ejecutándose en su proyecto:pip install pystrict
Entonces solo usa el decorador @strict:
from pystrict import strict @strict class Blah def __init__(self): self.attr = 1
fuente
Me gusta el "Frozen" de Jochen Ritzel. El inconveniente es que la variable isfrozen aparece al imprimir una clase .__ dict. Resolví este problema de esta manera creando una lista de atributos autorizados (similar a las ranuras ):
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
fuente
El
FrozenClass
de Jochen Ritzel es genial, pero llamar_frozen()
cuando se inicializa una clase cada vez no es tan genial (y debes correr el riesgo de olvidarlo). Agregué una__init_slots__
función: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
fuente