Em Java e em C++ há um bom motivo para que os atributos sejam sempre privados: preservar a interface das classes.Por exemplo, imagine que temos uma classe que representa o registro de uma pessoa. Nela temos a idade. Sendo um número inteiro, poderia ser um atributo público:
Person person;
person.age = 23;
std::cout << person.age << std::endl;Faz sentido. Agora imagine que mais tarde descobrimos que é preciso fazer um tratamento do valor recebido – para estar numa faixa aceitável, por exemplo…
Sem alterar a interface – e efetuar uma refatoração total de todo código onde ocorre instância – é impossível.
Então, em Java e C++ é importante usar métodos acessores e manter os atributos privados. Dessa forma qualquer alteração futura pode ser feita na classe sem alterar o restante do código:
Person person;
person.setAge(23);
std::cout << person.getAge() << std::endl;Propriedades
Já em Python isso não faz sentido!
Em Python, além de atributos e métodos, há as propriedades. Então atributos podem ser públicos e, caso seja necessário adicionar alguma lógica na leitura ou gravação do atributo, basta substituí-lo por uma propriedade, sem prejudicar a interface.
class Person:
def __init__(self):
self.name = ""
self.age = 0Substituindo por propriedades:
class Person:
def __init__(self):
self.name = ""
self.__age = 0
def _get_age(self):
return self.__age
def _set_age(self, age):
if 0 <= age < 150:
self.__age = age
age = property(_get_age, _set_age)E a interface permanece imutável!
Aviso: não use propriedade quando não for necessário! Use atributo público. Um pouco de desempenho não faz mal a ninguém, mesmo em scripts.
Aviso²: testei esse código no prompt e não funciona, mas funciona em script. Se alguém puder descobrir o que está acontecendo, agradeço.
[update 2009-08-26]Usando IPython funcionou corretamente, portanto não sei o que causou o erro. =P
Assim assim, valeu Thiago Coutinho por tê-lo identificado (acho que ainda ocorre durante a interpretação em linha do CPython tradicional).[/update]
Usando metaclasse
Uma forma de implementar automaticamente autopropriedades foi sugerida por Guido van Rossum em Unificando Tipos e Classes (original).
Usaremos uma metaclasse:
class autoprop(type):
def __init__(cls, name, base, dict):
super(autoprop, cls).__init__(name, base, dict)
props = {}
for method in dict:
if method.startswith("_get_") \
or method.startswith("_set_") \
or method.startswith("_del_"):
props[method[5:]] = True
for prop in props:
fget = getattr(cls, "_get_" + prop, None)
fset = getattr(cls, "_set_" + prop, None)
fdel = getattr(cls, "_del_" + prop, None)
setattr(cls, prop, property(fget, fset, fdel))A ideia por trás dessa metaclasse é identificar métodos iniciados por
_get_, _set_ ou _del_ e criar propriedades a partir daí. O método iniciado por _get_ representa a leitura, _set_ representa a escrita e _del_ representa a remoção.Nossa classe então ficaria assim:
class Person:
__metaclass__ = autoprop
def __init__(self):
self.name = ""
self.__age = 0
def _get_age(self):
return self.__age
def _set_age(self, age):
if 0 <= age < 150:
self.__age = ageUsando decoradores
Mas realmente, ao contrário da metaclasse para singleton, usar metaclasse para autopropriedade é como usar uma escopeta para espantar uma mosca.
Vamos usar decoradores!
O decorador
@property pode ser usado para criar propriedades para apenas leitura. Como precisamos de leitura e gravação, podemos usar @apply.É um pouco gambiarrático, mas funciona muito bem e deixa o código relativamente limpo:
class Person:
def __init__(self):
self.name = ""
self.__age = 0
@apply
def age():
def fget(self):
return self.__age
def fset(self, age):
if 0 <= age < 150:
self.__age = age
return property(**locals())A função deve ter o nome da propriedade. O método de escrita deve chamar-se
fset, o de leitura fget e o de remoção fdel. A parte mais feia desse código é o retorno. =)**
Fica aqui a dica!
[]'s
Cacilhas, La Batalema

14 comentários:
Muito legal o artigo.
Tentei rodar o exemplo do property mas acho que não deu certo:
>> p = Person()
>>
>> p.age = 151
>> p.age
151
>>
>> p.age = 15
>> p.age
15
Mmmmmmmmm...
Realmente! Tem alguma moscada no primeiro código de propriedades.
Preciso rever e entender qual foi a burrada que cometi.
Assim que acertar, eu aviso.
Por favor, anónimo, identifique-se de alguma forma para eu poder dar crédito aqui a você ter encontrado o bug.
[]'s
Cacilhas, La Batalema
Isso é realmente estranho!
Fiz um teste aqui…
No prompt não funciona; mas escrevendo um script e executando-o, funciona. =/
Essa é nova.
Se alguém puder testar e entender o que acontece, agradeço.
Vou apenas mudar os nomes dos métodos para o código ficar mais de acordo com os códigos seguintes (mas testei sem mudar e mudando, não fez diferença).
[]'s
Cacilhas, La Batalema
Que massaroka, cara!
Aqui também deu pau!
Eu coloquei prints dentro dos métodos, para saber se eles estavam sendo acessados ao menos, mas constatei que não (a não ser que eu tenha feito algo errado). O acesso direto ao método também não funfou.
Olha aí:
>>> class Person:
........def __init__(self): name = ""; __age = 0
........def _get_age(self): print "get"; return self.__age
........def _set_age(self, age):
............if 0 <= age < 150: print "set"; self.__age = age
........age = property(_get_age, _set_age)
...
>>> p = Person()
>>> p.age = 500
>>> p.age
500
>>> p._set_age(600)
>>> p.age
500
>>>
=/
Isso é muito sinistro…
É alguma moscada que eu dei. Já estou tão acostumado a usar decoradores e, antes disso, metaclasses, que perdi o hábito de usar diretamente property.
Mas metaclasses e decoradores funcionam. =)
[]'s
Cacilhas, La Batalema
Um adendo, já que eu não vi mencionarem no post ou nos comentários. A partir do Python 2.6 dá pra fazer assim também:
class C(object):
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
Depois de criar um property, o valor resultante tem uma propriedade setter e outra deleter que pode ser usada como decorator tb.
O exemplo acima foi retirado da própria seção what's new dos docs do Python 2.6
Eu, pessoalmente, acho bem limpa essa forma de fazer.
Opa!
Salve Kao!
Aqui estou usando ainda a versão 2.5, portanto não tenho como testar seu exemplo.
Ainda assim, valeu pela dica!
[]'s
Cacilhas, La Batalema
Esse post e aquele sobre singleton me deram bastante diversão no final de semana. :-) Como não conhecia o conceito de metaclasses, caí dentro no Google para tentar compreender melhor como funciona.
Pelo que entendi, metaclasses servem como modelos para a criação de classes assim como classes servem como modelos para produzirem instâncias de classes. Considerando que tudo em Python é objeto, seria a classe de uma classe. Usar uma metaclasse no lugar da padrão 'type' possibilita certa modificação no comportamento da classe como registar todas as instâncias em um registro comum ou alterar o processo de criação e inicialização como você fez no singleton. Aliás, pelo que percebi metaclasses são usadas para modificar o comportamento de criação dos objetos na maioria das vezes.
Se possível, gostaria que você corrigisse quaisquer erros conceituais que eu tenha cometido no parágrafo acima e que me indicasse literatura que julga interessante sobre o assunto. Estou até pensando em criar um texto "metaclasses para newbies", já que nenhum texto que li esse final de semana pareceu muito amigável para quem nunca usou Python ou metaclasses antes. Os textos que li são os seguintes:
[1] http://www.gidnetwork.com/b-137.html
[2] http://en.wikipedia.org/wiki/Metaclass
[3] http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html?page=1
[4] http://effbot.org/zone/metaclass-plugins.htm
[5] http://en.wikibooks.org/wiki/Python_Programming/MetaClasses
Dei uma olhada também em uma apresentação chamada 'Python Metaclasses: Who? Why? When?' que não lembro exatamente onde encontrei. O 'Unifying types and classes in Python 2.2' me pareceu um pouco árido, não consegui tirar muito dele.
Bom, é isso aí. Desculpe o comentário gigante.
Olá Guilherme!
É isso aí mesmo. O que você entendeu sobre metaclasses é exatamente o que eu entendo. =)
O magnífico texto Unificando Tipos e Classes do GvR traz alguns exemplos interessantes, apesar de metaclasses não serem exatamento o foco do texto.
Escrevi um artigo há uns anos sobre metaclasses que talvez lhe interesse.
[]'s
Cacilhas, La Batalema
Olá!
A charada é o seguinte, seu método get e set estão protegidos (_ no início do método), ou seja, só podem ser chamados pela classe ou subclasses e não servem como interface. ;-)
Pois então cara, se achar o bug do exemplo, posta aí.
Anônimo ;^)
Olá gente!
Desculpem-me ter ficado tanto tempo sem responder este thread, mas perdi o fio da meada. =)
Refiz os testes e tudo funcionou magicamente bem.
Não sei o que estava dando errado, mas funciona. =/
Semente, Python não possui atributos ou métodos protegidos, nem mesmo privados. O underscore antes de métodos e atributos apenas sugere que eles devam ser usados apenas de modo protegido, mas de forma alguma interfere no comportamento da linguagem.
Mesmo o underscore duplo não é exatamente privado, apesar de alterar um pouco (muito pouco) o comportamento.
Valeu por ter se identificado, Thiago!
[]'s
Cacilhas, La Batalema
La Batalema, o fato é que, retirando o _, você resolve o bug. ;-)
Até,
semente
Semente,
O fato é que funciona tirando ou não.
A verdadeira questão é: por que não estava funcionando antes?
[]'s
Cacilhas, La Batalema
Postar um comentário