13 de dezembro de 2008

Atributos Privados em Python


O encapsulamento provê uma forma de distinguir entre a especificação e a implementação dos métodos de um objeto. Quem leu a postagem Getters e Setters já está por dentro deste assunto. Agora, precisamos aprender a implementar isso em Python.

A primeira dúvida que surge, para quem nunca programou orientado a objetos (OO) em Python, é com relação modificadores de acesso a atributos, como os três Pês de Java: Public, Protected e Private. Por padrão, em Python, um atributo sempre é público e, pelo que já vi por aí, os programadores desta linguagem preferem deixá-los assim, embasados na frase do Zen de Python que diz:

Explícito é melhor que implícito.


Ideologias à parte, o fato é que o programador pode precisar limitar o acesso a um atributo. Para isso, ele pode fazer uso de um underscore no início do nome do atributo, criando um nome como _attribute. Esse underscore não impede que o atributo seja acessado de fora do objeto e, tampouco, que seja modificado. Contudo, um atributo nesta forma é um aviso para programadores Python: "Hey! Cuidado com este atributo! Ele é especial!"

Como pode ser percebido, o undercore no início do atributo é apenas uma notação usada por programadores Python e, caso não seja respeitado, não possui finalidade alguma. Entretanto, há uma forma de se definir atributos privados em Python. Para isso, basta adicionar dois underscores no início do nome do atributo, criando algo como __attribute. Fazendo isso, caso um usuário da classe tente acessar aquele atributo diretamente, o interpretador retorna um erro de atributo não encontrado. Em caso de tentativa de alteração direta do valor, nenhuma mensagem de erro é retornada, mas a atribuição não ocorre. Mas... Lembra-se da história do explícito ser melhor que o implícito? Há uma forma pouco trivial de burlar esse falso atributo privado.

Acontece que, quando o interpretador Python encontra um atributo com dois underscores no início do nome, ele executa uma operação chamada name mangling, que, em vez de criar o atributo com os dois underscores no início do nome, cria um atributo com nome igual a underscore + nome da classe + dois underscores + nome do atributo. Assim, para acessos de dentro da classe, o atributo pode ser invocado com os dois underscores, mais o nome do atributo e, de fora da classe, o formato de nome longo deve ser usado.

Para fixar estes conceitos, tomemos como exemplo a listagem de código abaixo:
class Clock:
    def __init__(self):
        self.second = 4
        self._minute = 7
        self.__hour = 0

clock = Clock()

print "clock object is: %d:%d:%d\n" % (clock._Clock__hour, clock._minute,
                                       clock.second)

# Accessing attribute second...
clock.second = "Inconsistent"
print "second attribute's value is:", clock.second

# Accessing attribute _minute...
clock._minute = "Don't change directly attributes starting with _!"
print  "_minute attribute's value is:", clock._minute

# Accessing attribute __hour...
clock._Clock__hour = "This is just an example!"
print  "__hour attribute's value is:", clock._Clock__hour

print "\n---\n"

clock.__hour = "It doesn't produce errors, but has no effects!"
print  "__hour attribute's value is:", clock._Clock__hour

print "\n---\n"

# When no matching attribute is found, a
# +new one is created. In this case, __hour.
print  "__hour attribute's value is:", clock.__hour
Listagem 1. Código de exemplo.

Nas linhas de 1 a 7, temos a declaração da classe e a instanciação de um objeto da classe recém criada, sucedida pela impressão dos atributos do objeto nas linhas 9 e 10. A seguir, nas linhas 13 e 14, é mostrado como é prejudicial permitir acesso indiscriminado a atributos da classe. O atributo second recebe uma string e logo em seguida é impresso na tela, comprovando a sua alteração para um estado de inconsistência. Nas linhas 17 e 18 uma operação similar é feita com o atributo _minute, exemplificando que a inserção de um underscore no início do nome do atributo não impede que o mesmo tenha seu valor modificado e seja acessado de fora da classe. Nas linhas 21 e 22 é explicitado como o interpretador Python cria nomes alternativos para atributos que começam com um duplo underscore, mas que não impede que tais atributos sejam acessados de fora da classe. Para completar, nas linhas 26, 27 e 33, é mostrado que a tentativa de se modificar o atributo __hour (linha 26) não produz efeitos aparentes (linha 27), mas que, na verdade, a tentativa de modificar um atributo inexistente, acarreta na criação deste atributo (linha 33) no objeto (não na classe).

Diferente de Java, Python é uma linguagem que dá liberdade ao programador para acessar atributos de objetos. Contudo, como disse o tio Ben, "Grandes poderes trazem grandes responsabilidades.", assim é preciso ter cautela ao acessar estes atributos. Dessa forma, caso o projetista da classe não tenha definido o acesso a um atributo, o usuário pode manipulá-lo sem medo. Caso um ou dois underscores tenham sido adicionados no início do nome do atributo, consulte a documentação da classe, para saber como acessar e alterar (se possível), o valor dele. É preciso acatar estas recomendações, pois, caso contrário, o próprio usuário pode sofrer com efeitos colaterais desta subversão.

Série Orientação a Objetos: Acessos a Atributos

Leia Também

2 comentários:

  1. Fala brow!

    Zezim e sua saga...kkkk

    Kra, não sou nenhum craque no assunto ainda, assim como você estou aprendendo Python agora(sem contar que estou atrás de vc - no bom sentido é claro), mas quando vc falou sobre os atributos com um _ no começo, _atributo, senti falta de vc mencionar que as instruções de importação com a clausula from, e.g.:

    from seu_modulo import *

    Neste caso, estes atributos não seriam importados, na sua classe clock, o atributo minute não seria importado.

    Já que vc estava falando sobre atributos, não sei se vc vai falar a parte, o atributos __system__, que são do sistema.

    Embora como vc citou, com name mangling, todo modulo mantem seus atributos e módulos, em __dict__, com esse dicionário pode-se acessar os atributos e métodos.

    Abraço brow.

    ResponderExcluir
  2. Papo ruim, heim?! rs

    Cara, acho que aqui, temos uma confusão de conceitos. No caso, os underscores no início do nome do atributo fazem efeito somente dentro da classe onde ele foi declarado. Você citou underscores no início de funções, variáveis, clases etc., de módulos. Então o escopo que eu estou tratanto é classe. O seu é módulo. Sacou a diferença?! Não vejo por que eu vou importar um atributo de classe, sendo que o objetivo da mesma é de instanciar objetos.

    Não confunda atributos de objetos com atributos de módulos, sendo que estes eu chamaria de variáveis, não atributos...

    Sobre o __dict__, pode deixar que vou falar dele, sim! Aguarde!

    Abraço!

    ResponderExcluir