¿Cómo restringir la configuración de un atributo fuera del constructor?

Quiero prohibir más asignaciones en algunos atributos de una clase después de que se haya inicializado. Por ejemplo; nadie puede asignar explícitamente ningún valor a la propiedad ‘ssn’ (número de seguridad social) después de que la instancia de la persona ‘p’ se haya inicializado. _ setattr _ también se llama mientras se asigna el valor dentro del método _ init _ , por lo tanto no es lo que quiero. Me gustaría restringir sólo otras tareas. ¿Cómo puedo lograr eso?

class Person(object): def __init__(self, name, ssn): self.name = name self._ssn = ssn def __setattr__(self, name, value): if name == '_ssn': raise AttributeError('Denied.') else: object.__setattr__(self, name, value) >> p = Person('Ozgur', '1234') >> AttributeError: Denied. 

La forma habitual es usar un atributo “privado” que comienza con un guión bajo y una propiedad de solo lectura para el acceso público:

 import operator class Person(object): def __init__(self, name, ssn): self.name = name self._ssn = ssn ssn = property(operator.attrgetter("_ssn")) 

Tenga en cuenta que esto realmente no impide que nadie cambie el atributo _ssn , pero el _ principal _ssn que el atributo es privado.

Puede omitir setattr asignando directamente el diccionario de instancia en el constructor. Por supuesto, este truco siempre se puede usar para falsificar cualquier otro aro que emplee para hacer que _ssn sea de solo lectura (después de una escritura inicial)

  class Person(object): def __init__(self, name, ssn): self.name = name self.__dict__['_ssn'] = ssn def __setattr__(self, name, value): if name == '_ssn': raise AttributeError('Denied.') else: object.__setattr__(self, name, value) 

Python no admite atributos privados o protegidos. Es necesario implementar el protocolo descriptor en su lugar. La biblioteca estándar proporciona decoradores para hacer eso de manera sucinta.

Simplemente declare el atributo con dos guiones bajos delante del método init . Se llama denominación de nombres e impide que se pueda acceder al atributo a través de __ssn, aunque en este caso, _Person__ssn todavía puede acceder y modificarlo. Sin embargo, si no define explícitamente un establecedor, se generará un AttributeError.

Por supuesto, si alguien tiene la intención de hacer un mal uso de la API, esa persona puede hacerlo si tiene mucha intención. Pero no sucederá por accidente.

 import re class Person: """Encapsulates the private data of a person.""" _PATTERN = re.COMPILE(r'abcd-efgh-ssn') def __init__(self, name, ssn): """Initializes Person class with input name of person and his social security number (ssn). """ # you can add some type and value checking here for defensive programming # or validation for the ssn using regex for example that raises an error if not self._PATTERN.match(ssn): raise ValueError('ssn is not valid') self.__name = name self.__ssn = snn @property def name(self): return self.__name @name.setter def name(self, value): self.__name = value @property def ssn(self): return self.__ssn >>> p = Person('aname', 'abcd-efgh-ssn') >>> p.ssn 'abcd-efgh-ssn' >>> p.ssn = 'mistake' AttributeError: 'can't set attribute' 

Solo señalando que todavía podríamos modificar _ssn .

Los objetos tienen el atributo especial, __dict__ , que es un diccionario que asigna todos los atributos de instancia del objeto con sus valores correspondientes. Podemos agregar / actualizar / eliminar atributos de instancia directamente modificando el atributo __dict__ de un objeto.

Todavía podemos modificar _snn así:

 p = Person('Ozgur', '1234') p.__dict__.get('_ssn') # returns '1234' p.__dict__['_ssn'] = '4321' p.__dict__.get('_ssn') # returns '4321' 

Como podemos ver, aún pudimos cambiar el valor de _ssn . Por diseño, no hay una forma directa, si la hay, de evitar el acceso a los atributos en Python en todos los casos.

Mostraré una forma más común de restringir el acceso a los atributos utilizando la propiedad () como decorador:

 class Person(object): def __init__(self, name, ssn): self.name = name self._ssn = ssn @property def ssn(self): return self._ssn @ssn.setter def ssn(self, value): raise AttributeError('Denied') >> p = Person('Ozgur', '1234') >> p.ssn >> '1234' >> p.ssn = '4321' >> AttributeError: Denied 

¡Espero que esto ayude!