25 de maio de 2013

Um ou dois sublinhados?

Muita gente confunde o uso de nomes iniciados com o caractere sublinhado, o underscore.

Essa é uma das convenções adotadas em Python. Mas o uso vai além de simples convenções. O interpretador Python se baseia em regras de nomes para determinar alguns comportamentos.

Na seção Reserved classes of identifiers, vemos a citação de três formas de nomeação que têm significados especiais:
  1. Nomes começados e terminados com dois underscores: __*__
  2. Nomes começados com um underscore: _*
  3. Nomes começados com dois underscores: __*

__*__

Esses são nomes começados e terminados com dois underscores. Eles são conhecidos como "métodos dunder". "Dunder" significa "double underscore".

Exemplos: __init__ (lê-se "dunder init"), __next__ ("dunder next"), __getattr__, __del__, __str__, etc. Veja todos eles na seção Special method names.

Qualquer identificador nomeado dessa forma, é considerado reservado para a linguagem Python.

Portanto, não crie variáveis, nem funções, nem métodos, nem classes, nem nada que o nome comece e termine com dois underscores. O manual avisa que isso pode causar problema de compatibilidade com a linguagem, sem nenhum aviso.

Bem, isso já é um aviso, certo?

_*

Nomes começados com um underscore, quando criados dentro de um módulo (ou pacote), não são importados quando usamos o comando from modulo import *.

Se você está criando um módulo e quer que alguma função, variável ou classe não seja importada automaticamente, nomeie-a iniciando com um underscore. Exemplo: _minha_funcao_interna().

Usando o mesmo conceito de "interno", métodos e variáveis internas de uma classe que não devem ser acessados de fora dela, também devem começar com um underscore. Mas isso não os torna privados. Nesse caso, é apenas uma convenção mesmo.

__*

Nomes começados com dois underscores são fonte de erro de interpretação e foram eles que me motivaram a escrever esse post. Eles são nomes de métodos ou variáveis privados de uma classe.

Quando variáveis ou métodos com nomes assim são definidos dentro de uma classe, eles são modificados e têm o nome da classe inserido no início. Preciso mostrar isso com um exemplo:

>>> class Pessoa(object):
...     def __init__(self, nome):
...         self.__nome = nome
... 
...     def pega_nome(self):
...         return self.__nome
...
>>> ele = Pessoa('Pedro')
>>> ele.pega_nome()
'Pedro'
>>> ele.__nome
Traceback (most recent call last):
  File "", line 1, in 
    ele.__nome
AttributeError: 'Pessoa' object has no attribute '__nome'


Aparentemente esse erro mostrado é estranho, não é? Afinal, temos uma variável __nome e o método pega_nome() mostrou o conteúdo dela.

Vamos ver, então, como essa variável foi criada dentro da instância de Pessoa():
>>> dir(ele)
['_Pessoa__nome', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__',
 '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
 '__str__', '__subclasshook__', '__weakref__']


Viu ali, no início? Tem uma variável chamada _Pessoa__nome. É isso que o Python faz com variáveis ou métodos iniciados com dois underscores, dentro de uma classe. Ele insere o nome da classe antes do nome da variável ou método que você criou.

Agora vamos refazer a classe Pessoa() e vamos criar uma classe filha dela, com o método pega_nome() na filha e ver o que acontece quando tentarmos acessar a variável __nome.
>>> class Pessoa(object):
...     def __init__(self, nome):
...         self.__nome = nome
... 
>>> class Filha(Pessoa):
...     def pega_nome(self):
...         return self.__nome
...
>>> ele = Filha('Pedro')
>>> ele.pega_nome()
Traceback (most recent call last):
  File "<pyshell>", line 1, in <module>
    ele.pega_nome()
  File "<pyshell>", line 3, in pega_nome
    return self.__nome
AttributeError: 'Filha' object has no attribute '_Filha__nome'
>>>


Viu? A classe filha não conseguiu acessar a variável __nome criada na classe mãe. É para isso que o interpretador modifica o nome de variáveis iniciadas com dois underscores: para protegê-los e torná-los realmente privados. Nada fora da classe onde eles foram criados pode acessá-los, a não ser que seja explicitamente, assim:
>>> print ele._Pessoa__nome
'Pedro'


Então, se você quer criar uma classe e ela tem a possibilidade de ser extendida, não crie métodos nem variáveis iniciados com dois underscores. A não ser que você tenha certeza que eles nunca serão usados nas classes filhas.

Eu sou Vinicius Assef, um programador do século passado que gosta de Python, pratica Lean Development e acredita em Deus. Você pode me contactar por email ou twitter.

5 comentários:

Marcadores