| Author: | Michael Fötsch (tradução de Walter Rodrigo de Sá Cruz e revisão por Rodrigo Cacilhas) |
|---|---|
| Date: | Wed Apr 18 11:48:20 2007 |
Tradução do artigo em http://www.geocities.com/foetsch/python/new_style_classes.htm
Classes new-style são parte de um esforço de unificar os tipos built-in e as classes definidas pelos usuários na linguagem de programação Python. Classes new-style estão aí desde o Python 2.2 (não são tão novas assim), então, é hora de tirar vantagem das novas possibilidades.
Uma classe new-style é derivada, direta ou indiretamente, de um tipo built-in (Algo que não era possível antes do Python 2.2.). Os tipos built-in incluem tipos como:
A classe base para classes todas as classes new-style é chamada object. Todos os exemplos a seguir são new-style classes:
class NewStyleUserDefinedClass(object):
pass
class DerivedFromBuiltInType(list):
pass
class IndirectlyDerivedFromType(DerivedFromBuiltInType):
pass
Classes new-style oferecem:
Tentarei ser breve, porém dando a você informação o suficiente para que você use essas características da linguagem. No lugar de longas descrições, exemplos práticos. Uma vez seu interesse seja despertado, você pode verificar a seção Referências para materiais mais detalhados sobre esse tópico.
Uma propriedade é um atributo que é definida por métodos get/set. O conceito é simples e bem conhecido em outras linguagens. Uma propriedade é definida assim:
class ClassWithProperty(object):
...
TheProperty = property(fget=<the get method>,
fset=<the set method>,
fdel=<the set method>,
doc=<the docstring>)
A assinatura do descritor da propriedade é property(fget=None, fset=None, fdel=None, doc=None). Se nenhum dos métodos é especificado, uma exceção do tipo AttributeError é lançada quando é tentada a respectiva operação. Por exemplo, para definir uma propriedade como somente leitura, você deve especificar o fget mas não o fset. (Propriedades somente de escrita são possíveis, embora um método regular consiga o mesmo resultado de uma forma menos complexa.)
Aqui tem um exemplo mais completo:
class ClassWithProperty(object):
def __SetTheProperty(self, value):
print "Setting the property"
self.__m_the_property = value
def __GetTheProperty(self):
print "Getting the property"
return self.__m_the_property
def __DelTheProperty(self):
print "Deleting the property"
del self.__m_the_property
TheProperty = property(fget=__GetTheProperty,
fset=__SetTheProperty,
fdel=__DelTheProperty,
doc="The property description.")
def __GetReadOnlyProperty(self):
return "This is a calculated value."
ReadOnlyProperty = property(fget=__GetReadOnlyProperty)
A propriedade é usada assim:
>>> c = ClassWithProperty() >>> c.TheProperty = 10 Setting the property >>> print c.TheProperty Getting the property 10 >>> del c.TheProperty Deleting the property >>> # The property itself is still there after deleting >>> c.TheProperty = 5 Setting the property >>> c.TheProperty.__doc__ 'The property description.' >>> print c.ReadOnlyProperty This is a calculated value. >>> c.ReadOnlyProperty = 100 Traceback (most recent call last): File "<interactive input>", line 1, in ? AttributeError: can't set attribute
Nota: Não esqueça de derivar sua classe de object, se não as propriedades não funcionarão.
Não há muito o que explicar sobre métodos estáticos; eles se comportam como no C++. Um método estático é definido usando o descritor staticmethod:
class MyClass(object):
def SomeMethod(x):
print x
SomeMethod = staticmethod(SomeMethod)
>>> MyClass.SomeMethod(15)
15
>>> obj = MyClass()
>>> obj.SomeMethod(15)
15
Você deve realmente considerar a criação de um método estático sempre que o método não faz uso substancial da instância (self).
Um método de classe é similar ao método estático no fato que não tem o argumento self. Porém, ele recebe uma classe como seu primeiro argumento. Por convenção, esse argumento é chamado cls. Um método de classe é definido usando o descritor classmethod:
class MyClass(object):
def SomeMethod(cls, x):
print cls, x
SomeMethod = classmethod(SomeMethod)
class DerivedClass(MyClass):
pass
>>> MyClass.SomeMethod(15)
__main__.MyClass 15
>>> obj = MyClass()
>>> obj.SomeMethod(15)
__main__.MyClass 15
>>> DerivedClass.SomeMethod(150)
__main__.DerivedClass 15
Na última chamada, você pode ver que é somente a classe envolvida em fazer a chamada de método que define o valor do argumento cls. Isso acontece a despeito do fato que o método foi definido em uma classe diferente.
Já vimos três tipos diferentes de descritores:
Mas o que é exatamente um descritor?
Em geral, um descritor é um atributo de objeto com «comportamento de ligação». É um objeto cujo acesso de atributos foi sobrescrito por métodos no protocolo de descritor. Esses métodos são __get__, __set__, e __delete__. Se algum desses métodos está definido para um objeto, dizemos que ele é um descritor. [1]
Ao executar a atribuição x.m = y, m pode ser um objeto que define um método __set__. Nesse caso, esse método é chamado para executar a atribuição.
O exemplo a seguir mostra como definir cada um dos três métodos do protocolo do descritor:
class MyDescriptor(object):
def __get__(self, obj, type=None):
print "get", self, obj, type
return "The value"
def __set__(self, obj, value):
print "set", self, obj, val
return None
def __delete__(self, obj):
print "delete", self, obj
return None
class SomeClass(object):
m = MyDescriptor()
Nota: Ambas as classes devem ser derivadas de object.
Agora nós podemos começar a usar o descritor:
>>> x = SomeClass() >>> print x.m get <__main__.MyDescriptor object at 0x12345678> <__main__.SomeClass object at 0x23456789> <class '__main__.SomeClass'> The value >>> x.m = 1000 set <__main__.MyDescriptor object at 0x12345678> <__main__.SomeClass object at 0x23456789> 1000 >>> del x.m delete <__main__.MyDescriptor object at 0x12345678><__main__.SomeClass object at 0x23456789>
Aqui está como o método descritor é chamado:
Algumas precauções:
Nota: No seu HOWTO [2], Raymond Hettinger tem algumas coisas a dizer sobre a diferença entre descitores de dados e descritores que não são de dados:
Se um objeto define __get__ e __set__, ele é considerado como um descritor de dados. Descritores que somente definem o método __get__ são chamados descritores que não são de dados(eles são usados tipicamente para métodos, mas outros usos são possíveis).
Descritores de dados e descritores que não são de dados diferem em como as sobrescritas são calculadas com respeito as entradas no dicionário da instância. Se um dicionário de instância tem uma entrada com o mesmo nome do descritor de dados, o descritor de dados recebe a precedência. Se um dicionário de instância tem uma entrada com o mesmo nome com um descritor que não é de dados, e entrada no dicionário recebe a precedência.
Para criar um descritor somente leitura, defina os métodos __get__ e __set__ com o __set__ lançando uma exceção AttributeError quando chamado. Definir o método __set__ com um lançamento de exceção é suficiante para torná-lo um descritor de dados.
Porém, eu não consegui adicionar uma entrada no dicionário de tal forma que o __get__ de um descritor que não seja de dados não seja chamado. Talvez eu tenha tentado as coisas erradas, talvez eu tenha entedido algo errado. Enquanto isso, vou apenas seguir o conselho de Raymond sobre definir ambos __get__ e __set__ por descritores somente de leitura.
Uma novidade interessante que eu descobri no documento de Guido van Rossum sobre classes new-style [3] são os slots Eis como eles funcionam:
class X(object):
__slots__ = ["m", "n"]
>>> x = X()
>>> x.m = 10
>>> x.n = 10
>>> x.k = 3
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
AttributeError: 'X' object has no attribute 'k'
__slots__ reserva espaço para as variáveis listadas diretamente na instância. Classes que definem slots não tem um dicionário de instância (__dict__). Se você tenta atribuir algo para um atributo que não exista __slots__, você recebe um erro. Isso pode ser bem útil para classes semelhantes a estruturas, pois previne problemas com nomes de atributos escritos incorretamente.
O propósito principal parece ser classes que derivam de tipos built-in. Por exemplo, um dicionário derivado pode ter apenas alguns slots para todos os atributos adicionais que ele precisa. Não é necessário ser criado um segundo __dict__ para esses atributos, o que economiza espaço.
Fique alerta: um slots em uma classe derivada esconde um slots com o mesmo nome na classe base.
Se você é como eu, então você provavelmente sempre pensou o método __init__ como o equivalente Python do que é chamado de um construtor em C++. Isso porém, não é tudo.
Quando uma instância de uma classe é criada, Python primeiro executa o método __new__ da classe. __new__ é um método estático que é chamado com a classe como primeiro argumento. __new__ retorna uma nova instância da classe.
O método __init__ é chamado logo em seguida para inicializar a instância. Em algumas situações (pensa em pickle/unpicle), nenhuma inicialização é executada. Também, tipos imutáveis como um int ou str são completamente construídos pelo método __new__; seus métodos __init__ não fazem nada. Dessa forma, é impossível tripudear a imutabilidade chamando o método __init__ explicitamente depois da construção.
Há uma nova forma do Python manipular resolução de métodos em conexão com a herança múltipla. Pessoalmente, eu tento evitar a herança múltipla sempre que possível, então não vou detalhar isso aqui. Porém, o que eu aprendi é que quando alguém mais deriva multiplamente de minhas próprias classes, seria legal se minhas classes executassem o que é chamado de «super chamada cooperativa». Para encurtar, ao invés do antigo //<classe-base>.<método-herdado>//""(self)"", poderíamos usar //super(<a-própria-classe>, self).<método-herdado>//().
class BaseClass:
def Method(self):
pass
class DerivedClass:
def Method(self):
super(DerivedClass, self).Method()
Parece ser bem fácil.
Vimos como classes new-style podem ser usadas para:
Eu não mencionei metaclasses. Eu mesmo não tenho usado metaclasses, então é melhor perguntar aos especialistas. Há uma série de links para artigos sobre metaclasses na página de classes new-style no python.org [4].
Eu espero que esse artigo tenha sido útil. Para quaisquer questões, sugestões, ou comentários, sinta-se livre para me mandar um e-mail.
| [1] | Unificando tipos e classes no Python 2.2, por Guido Van Rossum http://www.python.org/2.2.3/descrintro.html |
| [2] | Guia How-To para descritores, por Raymond Hettinger http://users.rcn.com/python/download/Descriptor.htm |
| [3] | Classes New-style Classes no python.org. http://www.python.org/doc/newstyle.html |
| [4] | Orientação a Objetos no Python depois do 2.2, por Michael Hudson http://starship.python.net/crew/mwh/hacks/oop-after-python22-1.txt - Algum tipo de slide ASCII, muito conciso |
O autor era anteriormente um programador apenas de C++ que amaldiçoou Python por anos por não dispor de nada como a palavra-chave property do Delphi (o autor nunca almodiçoaria C++ por não ter propriedades).