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(object):
def __init__(self):
self.name = ""
self.age = 0
Substituindo por propriedades:
class Person(object):
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(object):
__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 = age
Usando 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. Funciona muito bem e deixa o código bastante limpo:class Person(object):
def __init__(self):
self.name = ""
self.__age = 0
@property
def age(self):
return self.__age
@age.setter
def age(self, age):
if 0 <= age < 150:
self.__age = age
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