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.
- Modularização do programa em arquivos;
- Uso do comando import
- Testes utilizando if __name__ == “__main__”:.
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.
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:
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.
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().
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()
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
--------------------------------