Modularização, testes e reuso

Objetivo

Ao final dessa aula você vai saber como modularizar um programa em Python em arquivos diversos, permitindo o teste de cada módulo de forma independente. Cada arquivo pode conter uma ou mais funções e classes, que podem ser “importadas” por outros arquivos facilitando o reuso de código.

Tópicos

A medida que aprendemos recursos mais avançados, a ferramenta utilizada nesse curso que lhe permite digitar e executar os programas dentro do próprio navegador (chamada de activecode) começa a apresentar algumas limitações. Nesses casos, sugerimos que você desenvolva os programas em seu próprio computador usando, por exemplo, o ambiente de desenvolvimento IDLE3. Siga as instruções contidas `nessa página<https://panda.ime.usp.br/python>`__ para instalar Python3 em seu computador.

Módulos em Python

O desenvolvimento de programas complexos exige um cuidado maior na sua estruturação e testes. A orientação a objetos facilita bastante essa estruturação. A abstração de partes do problema na forma de classes de objetos também simplifica o processo pois, uma vez que o comportamento de uma classe esteja definida, implementações diferentes podem ser testadas simplesmente substituindo uma implementação por outra.

O Python permite que partes diferentes de um programa sejam definidas em arquivos diferentes, chamados de módulos. O Python também oferece vários módulos nativos, como o math (que contém várias funções matemáticas), o statistics (que inclui várias rotinas estatísticas), o sys para acessar funções do sistema, etc. Para utilizar as funções e classes definidas nesses módulos utilize o comando import.

Mas o import pode ser utilizado também para carregar os seus próprios módulos. Por exemplo, poderíamos colocar cada classe que desenvolvemos no exercício da aula 19 (Simulador e Balde) em módulos separados e armazenados nos arquivos simulador.py e balde.py.

O conteúdo do arquivo balde.py poderia ser:

(aula20_modulo_balde)



Observe que, ao final do arquivo utilizamos o comando:

if __name__ == "__main__":
    # programa de teste da classe Balde

para incluir testes da classe Balde, ao invés de usar uma função main. A vantagem de usar if __name__ == “__main__”: é que, ao se importar o módulo balde, os testes não são executados, mas a classe pode ser utilizada.

Como funciona?

O interpretador Python executa todas as instruções que encontra mas leva em conta algumas atributos especiais que definem o estado do interpretador. Um deles é o atributo __name__, que recebe o nome especial __main__ quando o módulo é executado diretamente usando, por exemplo:

python3 balde.py

Ao executar o comando import, o conteúdo do módulo é carregado com o nome do módulo que deve ser diferente de __main__ (no caso, é balde). Dessa forma, os testes podem ser executados diretamente e, ao mesmo tempo, o módulo pode ser utilizado em outros módulos e programas.

Módulo Simulador

O arquivo simulador.py pode então importar balde.py da seguinte forma:

'''
Módulo Simulador mantem o estado da simulacao e
os procedimentos para simular. Note que a classe
Simulador "esconde" o modulo random da funcao main.
'''

import random
from balde import Balde

# definicao de constantes
CAP_MIN = 10
CAP_MAX = 51 # ja ajustado ao random
VOL_MIN = 1
VOL_MAX = 11 # ja ajustado ao random

class Simulador:
    def __init__(self, semente):
        random.seed(semente)
        capacidade = random.randrange(CAP_MIN, CAP_MAX)
        self.rec = Balde(capacidade)
        self.vol = 0
        self.avisou = False

    def sorteia(self):
        self.vol = random.randrange(VOL_MIN, VOL_MAX)
        print()
        print("Volume atual   : ", self.rec.vol)
        print("Volume sorteado: ", self.vol)
        return self.vol

    def deposita(self):
        ''' adiciona o ultimo volume sorteado self.vol
            ao balde e retorna True se o
            balde estiver cheio e False caso contrario.
            '''
        self.rec.deposita( self.vol )

        if self.rec.vol >= self.rec.cap/2 and not self.avisou:
            self.avisou = True
            print("O volume do balde atingiu/passou a metade.")

        return self.rec.cheio

    def finaliza(self):
        print("\nFim da simulacao")
        print("Capacidade do balde: %d" % self.rec.cap)
        print("Volume final: %d" % self.rec.vol)

        if self.rec.der > 0:
            print("Balde esta cheio e houve derramamento de agua")
            print("Volume derramado foi: %d" %(self.rec.der))
        else:
            if self.rec.cheio:
                print("Balde esta cheio e nao houve derramamento de agua")
            elif self.rec.cap - self.rec.vol >= self.vol:
                print("Balde nao esta cheio.")
                print("Havia espaco para o ultimo volume sorteado: %d" % self.vol)
            else:
                print("Balde nao esta cheio.")
                print("Nao havia espaco para o ultimo volume sorteado: %d" % self.vol)


if __name__ == "__main__":
    s = Simulador(123)
    v1 = s.sorteia()
    r1 = s.deposita()
    print(v1, r1)
    v2 = s.sorteia()
    r2 = s.deposita()
    print(v2, r2)
    s.finaliza()

Copie esses módulos balde.py e simulador.py para o seu computador e execute o módulo simulador.py (e balde.py também):

> python3 simulador.py
Volume atual   :  0
Volume sorteado:  5
5 False

Volume atual   :  5
Volume sorteado:  2
O volume do balde atingiu/passou a metade.
2 False

Fim da simulacao
Capacidade do balde: 13
Volume final: 7
Balde nao esta cheio.
Havia espaco para o ultimo volume sorteado: 2

Observe que a classe Simulador utiliza (immport) o módulo nativo random, e todas as funções desse módulo são referenciadas pelo nome do módulo, como random.seed() e random.randrange(). Poderíamos ter feito o mesmo com o módulo balde escrevendo

import balde

ao invés de

from balde import Balde

A vantagem de se utilizar a segunda forma (from balde import Balde) é que apenas a classe Balde é carregada e, com isso, não é mais necessário incluir o nome do módulo. Para criar um Balde basta escrever Balde(10) ao invés de balde.Balde(10). Se você desejar importar tudo que estiver em um módulo sem precisar escrever o nome do módulo, você pode utilizar o caractere especial * como por exemplo:

from balde import *

isso é razoável para que módulos pequenos fiquem bem integrados ao programa, mas para módulos grandes, o melhor é manter o nome do módulo indicando as funções desejadas explicitamente, como em random.seed().

Programa principal

O programa main, que você pode armazenar no arquivo enchedagua-main.py, seria:

from simulador import Simulador

def main():
    semente = int(input("Digite a semente para gerar numeros pseudo-aleatorios: "))
    jogo    = Simulador(semente)

    fim = False
    while not fim:
        jogo.sorteia()
        opcao = input("Deseja utilizar o volume sorteado? (s/n): ")
        if opcao == 's':
            fim = jogo.deposita()  # retorna True caso o balde fique cheio
        else:
            fim = True
    jogo.finaliza()

main()

Exercício 20.1

Você deve fazer esse exercício no seu computador. Recomendamos utilizar um editor Python como o IDLE3.

Reutilizando o módulo balde.py acima, crie uma classe Simulador2 no módulo simulador2.py para implementar o exercício programa Encha de água mais ainda.

O programa principal, que define o comportamento do novo simulador, é dado a seguir:

import simulador2

def main():
    # cria um objeto Simulador2 usando uma semente
    semente = int(input("Semente: "))
    jogo  = Simulador2(semente)
    fim = False

    # cabecalho de inicio da simulacao
    print("--------------------------------")
    print("Início da simulação\n")
    print("Em cada iteração as opções possíveis são: ")
    print("    0 para abandonar")
    print("    1 para depositar no recipiente 1")
    print("    2 para depositar no recipiente 2")
    print("    3 para depositar no recipiente 3")
    print("    4 para descartar a quantidade de água\n")

    while not fim:
        sim.sorteia()
        escolha = int(input("    Opção desejada: "))

        if escolha == 0:
            fim = True
        elif escolha == 4:
            fim = jogo.descarta()
        else:
            fim = jogo.deposita(escolha)

    jogo.finaliza()

Salve esse esse programa em um arquivo como maisagua-main.py e execute o programa após terminar de escrever e testar o seu módulo simulador2.py. Abaixo é dado um outro exemplo de simulação.

Semente: 123
--------------------------------

Início da simulação

Em cada iteração as opções possíveis são:
0 para abandonar
1 para depositar no recipiente 1
2 para depositar no recipiente 2
3 para depositar no recipiente 3
4 para descartar a quantidade de água

. . . . . . . . . . . . . . . . . . . . .
Iteração  : 1
Descartes : 0
Volumes ocupados : [ 0] [ 0] [ 0]
Quantidade disponibilizada: 7
Opção desejada: 1
Volumes ocupados : [ 7] [ 0] [ 0]
. . . . . . . . . . . . . . . . . . . . .
Iteração  : 2
Descartes : 0
Volumes ocupados : [ 7] [ 0] [ 0]
Quantidade disponibilizada: 5
Opção desejada: 1
Volumes ocupados : [12] [ 0] [ 0]
. . . . . . . . . . . . . . . . . . . . .
Iteração  : 3
Descartes : 0
Volumes ocupados : [12] [ 0] [ 0]
Quantidade disponibilizada: 2
Opção desejada: 1
Volumes ocupados : [14] [ 0] [ 0]
. . . . . . . . . . . . . . . . . . . . .
Iteração  : 4
Descartes : 0
Volumes ocupados : [14] [ 0] [ 0]
Quantidade disponibilizada: 1
Opção desejada: 1
Volumes ocupados : [15] [ 0] [ 0]
. . . . . . . . . . . . . . . . . . . . .
Iteração  : 5
Descartes : 0
Volumes ocupados : [15] [ 0] [ 0]
Quantidade disponibilizada: 7
Opção desejada: 1
Volumes ocupados : *21* [ 0] [ 0]
Escolha recipiente para depositar excedente de 1: 2
Volumes ocupados : *21* [ 1] [ 0]
. . . . . . . . . . . . . . . . . . . . .
Iteração  : 6
Descartes : 0
Volumes ocupados : *21* [ 1] [ 0]
Quantidade disponibilizada: 6
Opção desejada: 2
Volumes ocupados : *21* [ 7] [ 0]
. . . . . . . . . . . . . . . . . . . . .
Iteração  : 7
Descartes : 0
Volumes ocupados : *21* [ 7] [ 0]
Quantidade disponibilizada: 6
Opção desejada: 2
Volumes ocupados : *21* *11* [ 0]
Escolha recipiente para depositar excedente de 2: 3
Volumes ocupados : *21* *11* * 2*

Fim da simulação
Volumes ocupados : *21* *11* * 2*
Capacidades      :  21    11     2
Capacidade total : 34
Volume ocupado   : 34
Volume livre     : 0
Volume derramado : 0
Descartes        : 0
--------------------------------

Table Of Contents

Previous topic

Exercícios

Next topic

Tipos abstratos de dados