Um mecanismo fundamental em sistemas orientados a objetos modernos é herança: uma maneira de derivar classes novas a partir da definição de classes existentes, denominadas neste contexto classes-base. As classes derivadas possuem acesso transparente aos atributos e métodos das classes base, e podem redefinir estes conforme conveniente.
Herança é uma forma simples de promover reuso através de uma generalização: desenvolve-se uma classe-base com funcionalidade genérica, aplicável em diversas situações, e definem-se subclasses concretas, que atendam a situações específicas.
Classes Python suportam herança simples e herança múltipla. Os exemplos até agora evitaram o uso de herança, mas nesta seção é possível apresentar a sintaxe geral para definição de uma classe:
class nome-classe(base1, base2, ..., basen):
atributo-1 = valor-1
.
.
atributo-n = valor-n
def nome-método-1(self, arg1, arg2, ..., argn):
# bloco de código do método
.
.
def nome-método-n(self, arg1, arg2, ..., argn):
# bloco de código do método
Como pode ser observado acima, classes base são especificadas entre parênteses após o nome da classe sendo definida. Na sua forma mais simples:
class Foo:
a = 1
def cheese(self):
print "cheese"
def foo(self):
print "foo"
class Bar(Foo):
def bar(self):
print "bar"
def foo(self): # método redefinido
print "foo de bar!"
uma instância da classe Bar tem acesso aos métodos cheese(), bar() e foo(), este último sendo redefinido localmente:
>>> b = Bar()
>>> b.cheese()
cheese
>>> b.foo() # saída demonstra método redefinido
foo de bar! # em Bar
>>> b.bar()
foo
>>> print b.a # acesso transparente ao atributo
1 # definido em Foo
enquanto uma instância da classe Foo tem acesso apenas às funções definidas nela, foo() e cheese:
>>> f = Foo()
>>> f.foo()
foo
Para acessar os métodos de uma classe-base, usamos uma construção diferente para invocá-los, que permite especificar qual classe armazena o método sendo chamado. Seguindo o exemplo, vamos novamente a redefinir o método Bar.foo():
class Bar(Foo):
# ...
def foo(self):
Foo.foo(self)
print "foo de bar!"
Nesta versão, o método foo() inclui uma chamada ao método Foo.foo(), que conforme indicado pelo seu nome, é uma referência direta ao método da classe base. Ao instanciar um objeto desta classe:
>>> b = Bar()
>>> b.foo()
foo
foo de bar!
pode-se observar que são executados ambos os métodos especificados. Este padrão, aqui demonstrado de forma muito simples, pode ser utilizado em situações mais elaboradas; seu uso mais freqüente é para invocar, a partir de um construtor de uma classe, o construtor das suas classes-base.
Há duas funções particularmente úteis para estudar uma hierarquia de classes e instâncias:
Uma particularidade em Python, que deriva da forma transparente como variáveis são acessadas, é a distinção entre atributos definidos em uma classe, e atributos definidos em uma instância desta classe. Observe o código a seguir:
class Foo:
a = 1
A classe acima define uma variável a com o valor 1. Ao instanciar esta classe,
>>> f = Foo()
>>> print f.a
1
observa-se que a variável parece estar definida na instância. Esta observação convida a algumas indagações:
>>> f.a = 2
As respostas para estas perguntas são todas relacionadas a um mecanismo central em Python, que é o protocolo getattr. Este protocolo dita como atributos são transparentemente localizados em uma hierarquia de classes e suas instâncias, e segue a seguinte receita:
Uma vez compreendido este mecanismo, é possível elucidar respostas para as questões acima. No exemplo, a variável a está definida na classe Foo, e pelo ponto 2 acima descrito, é acessível como se fosse definida pela própria instância. Ao atribuir um valor novo a f.a, estamos definindo uma nova variável a no estado local da variável f, o que não tem nenhum impacto sobre a variável a definida em Foo, nem sobre novas instâncias criadas a partir desta.
Se o descrito acima parece confuso, não se preocupe; o mecanismo normalmente funciona exatamente da maneira que se esperaria de uma linguagem orientada a objetos. Existe apenas uma situação `perigosa', que ocorre quando usamos atributos de classe com valores mutáveis, como listas e dicionários.
class Foo:
a = [1,2]
Nesta situação, quando criamos uma instância a partir de Foo, a variável a pode ser alterada por meio desta instância. Como não foi realizada atribuição, a regra 4 descrita acima não se aplica:
>>> f = Foo()
>>> f.a.append(3)
>>> g = Foo()
>>> print g.a
[1, 2, 3]
e a variável da classe é de fato modificada. Esta particularidade é freqüentemente fonte de bugs difíceis de localizar, e por este motivo se recomenda fortemente que não se utilize variáveis de tipos mutáveis em classes.