En general, esta no es una buena idea por las razones que @yak mencionó en su comentario. Básicamente impide que el usuario proporcione argumentos válidos que tengan los atributos/el comportamiento correctos pero no están en el árbol de herencia codificado.
Dejando de lado el descargo de responsabilidad, hay algunas opciones disponibles para lo que está intentando hacer. El problema principal es que no hay atributos privados en Python. Por lo tanto, si solo tiene una referencia de objeto antiguo, por ejemplo self._a
, no puede garantizar que el usuario no lo configure directamente aunque haya proporcionado un sistema que sí lo hace. Las opciones a continuación demuestran cómo aplicar realmente la verificación de tipos.
Anulación __setattr__
Este método sólo será conveniente para un pequeño número (muy) de atributos que hacen esto para. El método __setattr__
es el que se llama cuando se utiliza la notación de puntos para asignar un atributo regular. Por ejemplo,
class A:
def __init__(self, a0):
self.a = a0
Si ahora hacemos A().a = 32
, sería llamar A().__setattr__('a', 32)
under the hood. De hecho, self.a = a0
en __init__
también usa self.__setattr__
. Usted puede usar esto para hacer cumplir la comprobación de tipos:
class A:
def __init__(self, a0):
self.a = a0
def __setattr__(self, name, value):
if name == 'a' and not isinstance(value, int):
raise TypeError('A.a must be an int')
super().__setattr__(name, value)
La desventaja de este método es que usted tiene que tener una separada if name == ...
para cada tipo que desea comprobar (o if name in ...
para comprobar varios nombres para un tipo determinado) . La ventaja es que es la forma más sencilla de hacer que sea casi imposible para el usuario eludir el control de tipo.
hacer que una propiedad
Las propiedades son objetos que sustituyen su atributo normal con un objeto descriptor (por lo general mediante el uso de una anotación). Los descriptores pueden tener los métodos __get__
y __set__
que personalizan cómo se accede al atributo subyacente. Esto es como tomar la rama correspondiente if
en __setattr__
y ponerla en un método que se ejecutará solo para ese atributo. Aquí está un ejemplo:
class A:
def __init__(self, a0):
self.a = a0
@property
def a(self):
return self._a
@a.setter
def a(self, value):
if not ininstance(value, int):
raise TypeError('A.a must be an int')
self._a = value
Una forma un poco diferente de hacer la misma cosa se puede encontrar en la respuesta de @ jsbueno.
Si bien usar una propiedad de esta manera es ingenioso y sobre todo resuelve el problema, presenta un par de problemas. La primera es que tiene un atributo "privado" _a
que el usuario puede modificar directamente, evitando su verificación de tipo. Este es casi el mismo problema que usar un eliminador y un colocador, excepto que ahora se puede acceder al a
como el atributo "correcto" que redirige al colocador detrás de las escenas, lo que hace menos probable que el usuario se meta con _a
. El segundo problema es que tiene un captador superfluo para hacer que la propiedad funcione como de lectura y escritura. Estos problemas son el tema de la pregunta this.
crear una verdadera descriptor Setter-Sólo
Esta solución es probablemente el conjunto más robusto. Se sugiere en el accepted answer a la pregunta mencionada anteriormente. Básicamente, en lugar de utilizar una propiedad, que tiene un montón de lujos y comodidades que no puede quitarse de encima, crear su propia anotación descriptor y el uso que de cualquier atributo que requieren la comprobación de tipos:
class SetterProperty:
def __init__(self, func, doc=None):
self.func = func
self.__doc__ = doc if doc is not None else func.__doc__
def __set__(self, obj, value):
return self.func(obj, value)
class A:
def __init__(self, a0):
self.a = a0
@SetterProperty
def a(self, value):
if not ininstance(value, int):
raise TypeError('A.a must be an int')
self.__dict__['a'] = value
Los alijos setter el valor real directamente en el __dict__
de la instancia para evitar recurrencias en sí mismo indefinidamente. Esto hace posible obtener el valor del atributo sin proporcionar un getter explícito. Dado que el descriptor a
no tiene el método __get__
, la búsqueda continuará hasta que encuentre el atributo en __dict__
. Esto garantiza que todos los conjuntos pasen por el descriptor/setter mientras que permite el acceso directo al valor del atributo.
Si usted tiene un gran número de atributos que requieren un control de este tipo, se puede mover la línea self.__dict__['a'] = value
en __set__
método del descriptor:
class ValidatedSetterProperty:
def __init__(self, func, name=None, doc=None):
self.func = func
self.__name__ = name if name is not None else func.__name__
self.__doc__ = doc if doc is not None else func.__doc__
def __set__(self, obj, value):
ret = self.func(obj, value)
obj.__dict__[self.__name__] = value
class A:
def __init__(self, a0):
self.a = a0
@ValidatedSetterProperty
def a(self, value):
if not ininstance(value, int):
raise TypeError('A.a must be an int')
actualización
Python3.6 hace esto para usted casi fuera de la caja: https://docs.python.org/3.6/whatsnew/3.6.html#pep-487-descriptor-protocol-enhancements
TL; DR
Para un número muy pequeño de atributos que necesitan verificación de tipo, anule __setattr__
directamente. Para una mayor cantidad de atributos, use el descriptor solo setter como se muestra arriba. Usar propiedades directamente para este tipo de aplicación presenta más problemas de los que resuelve.
La forma Pythonic es documentar la clase de forma adecuada. Si se establece un tipo de objeto incorrecto, el código fallará de todos modos. Por otro lado, el usuario puede usar un tipo que no pasaría las comprobaciones 'isinstance', pero de lo contrario está bien (pato typing). – yak