Introdução
A herança (inheritance) é uma ferramenta comumente utilizada para escrever programas extensíveis. Esta seção explora essa ideia.
This tutorial is from open-source community. Access the source code
A herança (inheritance) é uma ferramenta comumente utilizada para escrever programas extensíveis. Esta seção explora essa ideia.
A herança (inheritance) é utilizada para especializar objetos existentes:
class Parent:
...
class Child(Parent):
...
A nova classe Child é chamada de classe derivada (derived class) ou subclasse (subclass). A classe Parent é conhecida como classe base (base class) ou superclasse (superclass). Parent é especificado em () após o nome da classe, class Child(Parent):.
Com a herança (inheritance), você está pegando uma classe existente e:
No final, você está estendendo o código existente.
Suponha que esta é a sua classe inicial:
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
return self.shares * self.price
def sell(self, nshares):
self.shares -= nshares
Você pode alterar qualquer parte disso via herança (inheritance).
class MyStock(Stock):
def panic(self):
self.sell(self.shares)
Exemplo de uso (Usage example).
>>> s = MyStock('GOOG', 100, 490.1)
>>> s.sell(25)
>>> s.shares
75
>>> s.panic()
>>> s.shares
0
>>>
class MyStock(Stock):
def cost(self):
return 1.25 * self.shares * self.price
Exemplo de uso (Usage example).
>>> s = MyStock('GOOG', 100, 490.1)
>>> s.cost()
61262.5
>>>
O novo método substitui o antigo. Os outros métodos não são afetados. É tremendo.
Às vezes, uma classe estende um método existente, mas deseja usar a implementação original dentro da redefinição. Para isso, use super():
class Stock:
...
def cost(self):
return self.shares * self.price
...
class MyStock(Stock):
def cost(self):
## Check the call to `super`
actual_cost = super().cost()
return 1.25 * actual_cost
Use super() para chamar a versão anterior.
Atenção: No Python 2, a sintaxe era mais verbosa.
actual_cost = super(MyStock, self).cost()
__init__ e herança (inheritance)Se __init__ for redefinido, é essencial inicializar o pai.
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
class MyStock(Stock):
def __init__(self, name, shares, price, factor):
## Check the call to `super` and `__init__`
super().__init__(name, shares, price)
self.factor = factor
def cost(self):
return self.factor * super().cost()
Você deve chamar o método __init__() no super, que é a maneira de chamar a versão anterior, como mostrado anteriormente.
A herança é, por vezes, usada para organizar objetos relacionados.
class Shape:
...
class Circle(Shape):
...
class Rectangle(Shape):
...
Pense em uma hierarquia lógica ou taxonomia. No entanto, um uso mais comum (e prático) está relacionado à criação de código reutilizável ou extensível. Por exemplo, um framework pode definir uma classe base e instruí-lo a personalizá-la.
class CustomHandler(TCPHandler):
def handle_request(self):
...
## Custom processing
A classe base contém algum código de uso geral. Sua classe herda e personaliza partes específicas.
A herança estabelece uma relação de tipo.
class Shape:
...
class Circle(Shape):
...
Verifique a instância do objeto.
>>> c = Circle(4.0)
>>> isinstance(c, Shape)
True
>>>
Importante: Idealmente, qualquer código que funcionasse com instâncias da classe pai também funcionará com instâncias da classe filha.
objectSe uma classe não tem pai, você às vezes vê object usado como a base.
class Shape(object):
...
object é o pai de todos os objetos em Python.
*Nota: tecnicamente não é obrigatório, mas você frequentemente o vê especificado como uma herança de seu uso obrigatório no Python 2. Se omitido, a classe ainda herda implicitamente de object.
Você pode herdar de múltiplas classes, especificando-as na definição da classe.
class Mother:
...
class Father:
...
class Child(Mother, Father):
...
A classe Child herda características de ambos os pais. Existem alguns detalhes bastante complicados. Não faça isso a menos que saiba o que está fazendo. Algumas informações adicionais serão fornecidas na próxima seção, mas não vamos utilizar herança múltipla neste curso.
Um uso importante da herança é na escrita de código que se destina a ser estendido ou personalizado de várias maneiras - especialmente em bibliotecas ou frameworks. Para ilustrar, considere a função print_report() em seu programa report.py. Ela deve se parecer com isto:
def print_report(reportdata):
'''
Print a nicely formatted table from a list of (name, shares, price, change) tuples.
'''
headers = ('Name','Shares','Price','Change')
print('%10s %10s %10s %10s' % headers)
print(('-'*10 + ' ')*len(headers))
for row in reportdata:
print('%10s %10d %10.2f %10.2f' % row)
Quando você executar seu programa de relatório, você deve obter uma saída como esta:
>>> import report
>>> report.portfolio_report('portfolio.csv', 'prices.csv')
Name Shares Price Change
---------- ---------- ---------- ----------
AA 100 9.22 -22.98
IBM 50 106.28 15.18
CAT 150 35.46 -47.98
MSFT 200 20.89 -30.34
GE 95 13.48 -26.89
MSFT 50 20.89 -44.21
IBM 100 106.28 35.84
Suponha que você quisesse modificar a função print_report() para suportar uma variedade de formatos de saída diferentes, como texto simples, HTML, CSV ou XML. Para fazer isso, você poderia tentar escrever uma função gigantesca que fizesse tudo. No entanto, fazê-lo provavelmente levaria a uma bagunça não sustentável. Em vez disso, esta é uma oportunidade perfeita para usar a herança.
Para começar, concentre-se nas etapas envolvidas na criação de uma tabela. No topo da tabela, há um conjunto de cabeçalhos de tabela. Depois disso, aparecem linhas de dados da tabela. Vamos pegar essas etapas e colocá-las em sua própria classe. Crie um arquivo chamado tableformat.py e defina a seguinte classe:
## tableformat.py
class TableFormatter:
def headings(self, headers):
'''
Emit the table headings.
'''
raise NotImplementedError()
def row(self, rowdata):
'''
Emit a single row of table data.
'''
raise NotImplementedError()
Esta classe não faz nada, mas serve como uma espécie de especificação de design para classes adicionais que serão definidas em breve. Uma classe como esta é, por vezes, chamada de "classe base abstrata".
Modifique a função print_report() para que ela aceite um objeto TableFormatter como entrada e invoque métodos nele para produzir a saída. Por exemplo, assim:
## report.py
...
def print_report(reportdata, formatter):
'''
Print a nicely formatted table from a list of (name, shares, price, change) tuples.
'''
formatter.headings(['Name','Shares','Price','Change'])
for name, shares, price, change in reportdata:
rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]
formatter.row(rowdata)
Como você adicionou um argumento a print_report(), você também precisará modificar a função portfolio_report(). Altere-a para que ela crie um TableFormatter assim:
## report.py
import tableformat
...
def portfolio_report(portfoliofile, pricefile):
'''
Make a stock report given portfolio and price data files.
'''
## Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
## Create the report data
report = make_report_data(portfolio, prices)
## Print it out
formatter = tableformat.TableFormatter()
print_report(report, formatter)
Execute este novo código:
>>> ================================ RESTART ================================
>>> import report
>>> report.portfolio_report('portfolio.csv', 'prices.csv')
... crashes ...
Ele deve travar imediatamente com uma exceção NotImplementedError. Isso não é muito emocionante, mas é exatamente o que esperávamos. Continue para a próxima parte.
A classe TableFormatter que você definiu na parte (a) destina-se a ser estendida via herança. Na verdade, essa é a ideia principal. Para ilustrar, defina uma classe TextTableFormatter assim:
## tableformat.py
...
class TextTableFormatter(TableFormatter):
'''
Emit a table in plain-text format
'''
def headings(self, headers):
for h in headers:
print(f'{h:>10s}', end=' ')
print()
print(('-'*10 + ' ')*len(headers))
def row(self, rowdata):
for d in rowdata:
print(f'{d:>10s}', end=' ')
print()
Modifique a função portfolio_report() assim e experimente:
## report.py
...
def portfolio_report(portfoliofile, pricefile):
'''
Make a stock report given portfolio and price data files.
'''
## Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
## Create the report data
report = make_report_data(portfolio, prices)
## Print it out
formatter = tableformat.TextTableFormatter()
print_report(report, formatter)
Isso deve produzir a mesma saída de antes:
>>> ================================ RESTART ================================
>>> import report
>>> report.portfolio_report('portfolio.csv', 'prices.csv')
Name Shares Price Change
---------- ---------- ---------- ----------
AA 100 9.22 -22.98
IBM 50 106.28 15.18
CAT 150 35.46 -47.98
MSFT 200 20.89 -30.34
GE 95 13.48 -26.89
MSFT 50 20.89 -44.21
IBM 100 106.28 35.84
>>>
No entanto, vamos mudar a saída para outra coisa. Defina uma nova classe CSVTableFormatter que produz saída em formato CSV:
## tableformat.py
...
class CSVTableFormatter(TableFormatter):
'''
Output portfolio data in CSV format.
'''
def headings(self, headers):
print(','.join(headers))
def row(self, rowdata):
print(','.join(rowdata))
Modifique seu programa principal da seguinte forma:
def portfolio_report(portfoliofile, pricefile):
'''
Make a stock report given portfolio and price data files.
'''
## Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
## Create the report data
report = make_report_data(portfolio, prices)
## Print it out
formatter = tableformat.CSVTableFormatter()
print_report(report, formatter)
Você deve ver agora a saída CSV assim:
>>> ================================ RESTART ================================
>>> import report
>>> report.portfolio_report('portfolio.csv', 'prices.csv')
Name,Shares,Price,Change
AA,100,9.22,-22.98
IBM,50,106.28,15.18
CAT,150,35.46,-47.98
MSFT,200,20.89,-30.34
GE,95,13.48,-26.89
MSFT,50,20.89,-44.21
IBM,100,106.28,35.84
Usando uma ideia semelhante, defina uma classe HTMLTableFormatter que produz uma tabela com a seguinte saída:
<tr><th>Name</th><th>Shares</th><th>Price</th><th>Change</th></tr>
<tr><td>AA</td><td>100</td><td>9.22</td><td>-22.98</td></tr>
<tr><td>IBM</td><td>50</td><td>106.28</td><td>15.18</td></tr>
<tr><td>CAT</td><td>150</td><td>35.46</td><td>-47.98</td></tr>
<tr><td>MSFT</td><td>200</td><td>20.89</td><td>-30.34</td></tr>
<tr><td>GE</td><td>95</td><td>13.48</td><td>-26.89</td></tr>
<tr><td>MSFT</td><td>50</td><td>20.89</td><td>-44.21</td></tr>
<tr><td>IBM</td><td>100</td><td>106.28</td><td>35.84</td></tr>
Teste seu código modificando o programa principal para criar um objeto HTMLTableFormatter em vez de um objeto CSVTableFormatter.
Uma característica importante da programação orientada a objetos é que você pode conectar um objeto em um programa e ele funcionará sem ter que alterar nenhum dos códigos existentes. Por exemplo, se você escreveu um programa que esperava usar um objeto TableFormatter, ele funcionaria, independentemente do tipo de TableFormatter que você realmente fornecesse. Esse comportamento é, por vezes, referido como "polimorfismo".
Um problema potencial é descobrir como permitir que um usuário escolha o formatador que deseja. O uso direto dos nomes das classes, como TextTableFormatter, é frequentemente irritante. Assim, você pode considerar alguma abordagem simplificada. Talvez você incorpore uma instrução if- no código assim:
def portfolio_report(portfoliofile, pricefile, fmt='txt'):
'''
Make a stock report given portfolio and price data files.
'''
## Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
## Create the report data
report = make_report_data(portfolio, prices)
## Print it out
if fmt == 'txt':
formatter = tableformat.TextTableFormatter()
elif fmt == 'csv':
formatter = tableformat.CSVTableFormatter()
elif fmt == 'html':
formatter = tableformat.HTMLTableFormatter()
else:
raise RuntimeError(f'Unknown format {fmt}')
print_report(report, formatter)
Neste código, o usuário especifica um nome simplificado como 'txt' ou 'csv' para escolher um formato. No entanto, colocar uma grande instrução if- na função portfolio_report() dessa forma é a melhor ideia? Pode ser melhor mover esse código para uma função de uso geral em outro lugar.
No arquivo tableformat.py, adicione uma função create_formatter(name) que permita a um usuário criar um formatador, dado um nome de saída como 'txt', 'csv' ou 'html'. Modifique portfolio_report() para que fique assim:
def portfolio_report(portfoliofile, pricefile, fmt='txt'):
'''
Make a stock report given portfolio and price data files.
'''
## Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
## Create the report data
report = make_report_data(portfolio, prices)
## Print it out
formatter = tableformat.create_formatter(fmt)
print_report(report, formatter)
Tente chamar a função com diferentes formatos para garantir que ela esteja funcionando.
Modifique o programa report.py para que a função portfolio_report() aceite um argumento opcional que especifica o formato de saída. Por exemplo:
>>> report.portfolio_report('portfolio.csv', 'prices.csv', 'txt')
Name Shares Price Change
---------- ---------- ---------- ----------
AA 100 9.22 -22.98
IBM 50 106.28 15.18
CAT 150 35.46 -47.98
MSFT 200 20.89 -30.34
GE 95 13.48 -26.89
MSFT 50 20.89 -44.21
IBM 100 106.28 35.84
>>>
Modifique o programa principal para que um formato possa ser fornecido na linha de comando:
$ python3 report.py portfolio.csv prices.csv csv
Name,Shares,Price,Change
AA,100,9.22,-22.98
IBM,50,106.28,15.18
CAT,150,35.46,-47.98
MSFT,200,20.89,-30.34
GE,95,13.48,-26.89
MSFT,50,20.89,-44.21
IBM,100,106.28,35.84
$
Escrever código extensível é um dos usos mais comuns de herança em bibliotecas e frameworks. Por exemplo, um framework pode instruí-lo a definir seu próprio objeto que herda de uma classe base fornecida. Em seguida, você é instruído a preencher vários métodos que implementam vários pedaços de funcionalidade.
Outro conceito um tanto mais profundo é a ideia de "possuir suas abstrações". Nos exercícios, definimos nossa própria classe para formatar uma tabela. Você pode olhar para seu código e dizer a si mesmo "Eu deveria apenas usar uma biblioteca de formatação ou algo que outra pessoa já fez em vez disso!" Não, você deve usar AMBOS, sua classe e uma biblioteca. Usar sua própria classe promove o acoplamento fraco e é mais flexível. Contanto que seu aplicativo use a interface de programação da sua classe, você pode alterar a implementação interna para funcionar da maneira que desejar. Você pode escrever código totalmente personalizado. Você pode usar um pacote de terceiros. Você troca um pacote de terceiros por um pacote diferente quando encontra um melhor. Não importa - nenhum dos códigos do seu aplicativo será interrompido, desde que você preserve a interface. Essa é uma ideia poderosa e é uma das razões pelas quais você pode considerar a herança para algo como isso.
Dito isso, projetar programas orientados a objetos pode ser extremamente difícil. Para mais informações, você provavelmente deve procurar livros sobre o tópico de padrões de projeto (embora entender o que aconteceu neste exercício o levará bastante longe em termos de uso de objetos de uma forma praticamente útil).
Parabéns! Você concluiu o laboratório de Herança. Você pode praticar mais laboratórios no LabEx para aprimorar suas habilidades.