arrow_back Volver
Inicio keyboard_arrow_right Artículos keyboard_arrow_right Artículo

Atributos privados en Python

Eduardo Ismael Garcia

Full Stack Developer at Código Facilito.

av_timer 5 Min. de lectura

remove_red_eye 43974 visitas

calendar_today 27 Marzo 2020

Cuando comenzamos con el tema de Programación orientada a objetos es indiscutible, que tarde o temprano, llegaremos al concepto de modificadores de accesos. Esto para definir ciertos elementos como públicos o privados. Cada lenguaje de programación ( que acepte este paradigma por su puesto) tiene su propia forma de manejar los accesos. Por ejemplo, en GO, si queremos definir un elemento como público, es decir, que pueda ser accedido y modificado por otra parte del código, por ejemplo, fuera del módulo, colocaremos el nombre del elemento con su primera letra en Mayúscula.

var Username // Public
var password // Private

struct User { // Public
    Username string
        Age int
        password string // Private
}

Otro ejemplo puede ser Java, donde basta colocar las palabras reservada public o private para definir dónde puede, o no, ser utilizado un elemento.

public String username = 'Cody';
private String password = 'Password123';

y ahora ¿Qué pasa con Python? pues bien déjame decirte que con Python existe un pequeño "detalle" con esto de los modificadores de acceso. Hay desarrolladores que aseguran el lenguaje tiene la posibilidad de restringir el accesos a ciertos elemento, y de cierta manera tienen razón, pero no al estilo de Java por ejemplo. En Python no encontraremos elementos públicos o privados, simplemente elementos que deben ser respetados por los desarrolladores. Es por ello que en esta ocasión me gustaría que habláramos de como en Python podemos "emular" los modificadores de acceso.

Bien, sin más que decir, comencemos.

Publicos y privados

En Python, por default, todos los elementos dentro de una clase serán "publicos", es decir, podrán ser consultados y/o modifcados fuera de la clase. Esto aplica de igual forma tanto para elementos de clase como de la instancia. Basta con colocar ya sea, nuestra clase/instancia, punto y el elemento con el cual deseemos trabajar. Veamos un par de ejemplos.

class Circulo:
    pi = 3.1415 # Atributo de clase

>>> Circulo.pi
1415

Aquí otro ejemplo, pero con atributos de instancia.

class User:
    def __init__(self, username):
        self.username = username

cody = User('Cody')
print(cody.username)

cody.username = 'Cambio de nombre'
print(cody.username)

De igual forma esto aplica para los métodos.

class User:

    def __init__(self, username):
        self.username = username

    def set_username(self, username):
        self.username = username

    def get_username(self):
        return self.username

cody = User('Cody')
print(cody.get_username())

cody.set_username('Cambio de nombre')
print(cody.get_username())

Este ya es un código, mmmm, podemos decir, al estilo de Java.

Con este último ejemplo podemos concluir que tanto los métodos, set_username y get_username, así como el atributo, username, son "publicos". Pueden ser accedidos y modificados fuera de la clase.

Y, ¿Qué pasa si queremos definir un elemento como privado? En esos casos, por convención, haremos uso del guión bajo (_). El guión bajo debe colocarse como prefijo a nuestro elemento, de esta forma le indicamos a cualquier desarrollador que, ese elemento en particular, debe tratarse como privado, y no debe ser expuesto ni modificado externamente.

Ojo, esto es una convención, el intérprete en ningún momento negará el acceso a dicho elemento.

class User:

    def __init__(self, username, password):
        self.username = username
        self._password = password

    def set_username(self, username):
        self.username = username

    def get_username(self):
        return self.username

cody = User('Cody', 'password123')
print(cody._password)

En este caso, aunque indicamos que password es un atributo privado, si ejecutamos el programa no deberíamos tener ningún tipo de problema, e inclusive deberíamos visualizar la contraseña en consola.

El uso del guión bajo como prefijo aplica de igual manera para métodos.

Con esto en mente quizás te estes preguntando ¿ En qué casos haremos uso de variables o métodos privados? La respuesta es sencilla. Siempre que deseemos evitar que un usuario modifique el estado interno de la instancia, lo cual podría compromete al objeto, será necesario el uso de variable y métodos privados. Por ejemplo, en este caso, al mi clase poseer el atributo password, y el password ser un dato sensible, lo mejor que podemos hacer es asegurarnos que el atributo no pueda ser modificado ni accedido fuera de la clase; por ello es un excelente candidato a ser un atributo privado.

Otro ejemplo pudiera ser al atributo score dentro de un juego, un atributo que el cual no queremos el usuario modifique, esto para evitar trampas.

Ahora, ya sabemos que todos los elementos dentro de una clases, por default, son "públicos", si queremos que un desarrollador no modifique o mande a llamar algún elemento fuera de la clase, basta con colocar un guión bajo como prefijo, sin embargo, es probable, que tarde o temprano(si no es que ya lo hiciste) te topes con variables o métodos con un doble guión bajo (__). Esos casos son especiales, te explico.

Al utilizar un doble guión bajo, ya sea para una atributo o algún método, el intérprete de Python re nombra al elemento para evitar colisiones con las subclases. Aunque la idea original era esa, evitar colisiones, muchos desarrolladores (me incluyo) utilizamos el doble guión bajo (__) para prevenir accesos no autorizados.

class Demo:
    def __secret(self):
        print('Nadie puede saber!')

    def public(self):
        self.__secret()

class Child(Demo):
    def __secret(self):
        print('No puedo contarte!')

>>> demo = Demo()
>>> demo.__secret()     
AttributeError: 'Demo' object has no attribute '__secret'

>>> demo.public()
Nadie puede saber!

>>> child = Child() 
>>> child.public()
Nadie puede saber!

>>> dir(demo)  
['_Demo__secret_', 'public' .... ]

En este caso como podemos observar al colocar doble guión bajo al método secret() nuestra instancia no puede acceder a él, tenemos el error AtributoError, dándonos a entender que este elemento no existe. No es que no exista, simplemente que tiene otro nombre. Si listamos todos los atributos de la clase nos daremos cuenta que el atributo simplemente fue re nombrado, ahora posee el nombre de _Demo__secret\.

De igual forma, si inspeccionamos un poco más el código, podremos percatarnos que aunque la clase hija posea el método secret(), al igual que la clase padre, el método no es llamado aun cuando se ejecuta public().

Si ahora listamos los atributos de child no llevaremos una sorpresa (o quizás no).

>>> dit(child)
['_Child__secret',
 '_Demo__secret', ]

En la lista encontraremos dos atributos secret tanto para Child como para Demo. Si trazladamos esto a nuestro ejemplo de la clase User, la clase pudiera quedar de la siguiente manera.

class User:
    def __init__(self, username, password):
        self.username = username
        self.change_password(password)

    def change_password(self, password):
        import hashlib
        self.__encrypted_password = hashlib.sha512(password.encode('utf-8')).hexdigest()

    @property
    def password(self):
        return self.encrypted_password

Definimos dos atributos, username y encrypted password, un atributo público y uno privado respectivamente. De igual forma definimos una propiedad llamada password, una propiedad de solo lectura, con ella podremos exponer la contraseña solo para consultas.

>>> user = User('User', 'password123')
>>> user.password   
'bed4efa1d4fdbd954bd3705d6a2a78270ec9a52ecfbfb010c61862af5c76af1761ffeb1aef6aca1bf5d02b3781aa854fabd2b69c790de74e17ecfec3cb6ac4bf'

>>> user.change_password('change_password123')
>>> user.password    
'7870fb72b950a3619aa1c900076caa32a6e5cacaaf4f117ddb6e24ce6a0e971ac23083e83859a5e6ed7c9113dad7cc5bfa11e63a93edd5ae785f32e2b75805b5'

Recalcó, aún que el intérprete re nombre al elemento, aun así es posible acceder a él.

child._Child__secret() 

Y bien, de esta forma es como podremos trabajar los atributos y métodos públicos y privados en de Python. Aunque recordemos, todos los elementos de una clase, por default, están expuestos a consultas y modificaciones. Que el intérprete pueda re nombrar los elementos no significa que un desarrollador, con conocimientos, no pueda hacer uso de ellos. Así que recuerda, si te topas con un código donde encuentre un guión bajo o un doble guión bajo, respetalos, y trata a ese elemento como privado, por alguna razón el desarrollador antes de ti pensó que sería una muy buena idea hacerlo privado.