ICE-B
4 - Implementação
Implementação
Resumo
■ Exemplo: cálculo de pH, agora estruturado
■ Testes unitários
■ Ciclo de vida de um programa
Implementação
Calcular pH
Solução de ácido benzóico
C H COOH H + C H COO6 5 ⇔ + 6 5 −
■ Constante de dissociação,
■ Ignorando auto-dissociação da água:
■ Resolvemos equação quadrática,
• (só precisamos da solução positiva)
■ Depois calculamos o pH:
= 6.5 ×
K
a10
−5 = = ⇔ + x − = 0 Ka [H ][ CO ] + C 6H5 O− [C6H5COOH] x2 − x Ci x 2 K a CiKax =
−b±√2ab2−4ac pH = −lo ([g10 H+]) = −lo (x)g10Calcular pH
Compreender, generalizar e decompor
■ Generalização: Calcular o pH de um ácido fraco monoprótico
■ Decomposição: Obter a raíz positiva e calcular o pH
■ Criamos ficheiro phcalc.py com o "esqueleto" do programa
# coding: utf-8
-*-"""
Computes pH of an acid from concentration """
def positive_root(a, b, c):
"return positive root of quadratic equation"
def compute_pH(Ka, Ci):
"""
return pH value for weak acid from
dissociation constant and initial concentration """
Calcular pH
"Esqueleto" do programa
■ É uma boa ideia fazer primeiro um esboço do programa
• Permite ver se encaixa tudo e se é preciso decompor mais
■ Estas funções ainda não fazem nada mas este código já cria os objectos.
def positive_root(a, b, c):
"return positive root of quadratic equation"
■ A string logo a seguir à assinatura é a documentação
• Podem usar ''' ou """ se o texto ocupar várias linhas In : help(positive_root)
Help on function positive_root in module __main__: positive_root(a, b, c)
Calcular pH
Documentação do módulo
■ A string no início do módulo é útil como documentação
# coding: utf-8
-*-"""
Computes pH of an acid from concentration """
■ Depois de executar com F5, podemos importá-lo e pedir ajuda In : import phcalc
In : help(phcalc)
Help on module phcalc: NAME
phcalc - Computes pH of an acid from concentration FUNCTIONS
compute_pH(Ka, Ci)
Calcular pH
Implementar as funções
■ Depois de perceber os objectivos e o algoritmo e de decompor em tarefas podemos implementar cada função
■ Devemos começar pelas "pontas", aquelas que não dependem de nenhuma outra que tenhamos de implementar
def positive_root(a, b, c):
"return positive root of quadratic equation" root = (-b + (b**2 - 4*a*c)**0.5)/(2*a)
return root
■ Depois devemos testar:
In : positive_root(1,0,-1) #x**2 + 0x - 1
Out: 1.0
In : positive_root(1,-2,0) #x**2 - 2x + 0
Calcular pH
Implementar as funções
■ Depois de implementar e testar uma função podemos passar a outra que dependa desta
■ Como o cálculo do pH precisa do logaritmo, temos de importar log10
• É melhor fazer todos os import no início do módulo
# coding: utf-8
-*-"""
Computes pH of an acid from concentration """
from numpy import log10
def positive_root(a, b, c):
...
Calcular pH
Implementar as funções
■ Agora implementamos e testamos esta também
def compute_pH(Ka, Ci):
"""
return pH value for weak acid from
dissociation constant and initial concentration """
H = positive_root(1, Ka, -Ka*Ci) return -log10(H)
■ Testar com valores que permitam confirmar o resultado: In : compute_pH(6.5e-5,0.01)
Testes Unitários
Testar sempre cada função
■ Decompomos um problema complexo em problemas mais simples.
■ Mas depois vai ser preciso juntar tudo.
• Um erro descoberto só no final é mais difícil de encontrar e corrigir.
■ É preciso testar:
• Sempre que implementamos algo de novo.
• Sempre que corrigimos um erro, para confirmar.
Testes Unitários
Sugestão: automatizar os testes todos
■ Opção 1: uma função no final do nosso módulo
• Numa função para não correr os testes sempre que usamos o módulo ...
def tests():
"""
run unit tests on all functions """
print("positive_root, 1.0:", positive_root(1,0,-1)) print("positive_root, 2.0:",positive_root(1,-2,0)) print("compute_pH, 3.111:",compute_pH(6.5e-5,0.01))
■ É importante que se possa verificar o resultado também In : tests()
positive_root, 1.0: 1.0
positive_root, 2.0: 2.0
Testes Unitários
Sugestão: automatizar os testes todos
■ Opção 2: um módulo só para testes (phtests.py)
# coding: utf-8
-*-"""
run unit tests on all functions in phcalc """
from phcalc import positive_root, compute_pH
print("positive_root, 1.0:", positive_root(1,0,-1)) print("positive_root, 2.0:",positive_root(1,-2,0)) print("compute_pH, 3.111:",compute_pH(6.5e-5,0.01))
■ Basta correr com F5
• Neste caso não é preciso função porque o módulo é só para testes In : runfile('/pasta/phtests.py', wdir='/pasta')
Testes Unitários
Atenção: funções são criadas na execução do def...
def tests():
"""
run unit tests on all functions """
print("positive_root, 1.0:", positive_root(1,0,-1)) print("positive_root, 2.0:",positive_root(1,-2,0)) print("compute_pH, 3.111:",compute_pH(6.5e-5,0.01))
■ Sempre que alterarem alguma coisa no vosso programa precisam de criar novamente os objectos das funções alteradas
■ Para isso o interpretador tem de reler a assinatura da função e toda a "receita" no corpo da função
■ A forma mais prática de fazer isso no Spyder é F5
• (Grava e corre todo o ficheiro)
Testes Unitários
Atenção: funções são executadas só em nome( ... )
In : tests()
positive_root, 1.0: 1.0
positive_root, 2.0: 2.0
compute_pH, 3.111: 3.11104555393
■ Quando se corre módulo as funções são criadas mas não usadas
■ A menos que o módulo inclua também chamadas às funções
# coding: utf-8
-*-...
def tests():
print("positive_root, 1.0:", positive_root(1,0,-1)) print("positive_root, 2.0:",positive_root(1,-2,0)) print("compute_pH, 3.111:",compute_pH(6.5e-5,0.01)) tests()
Testes Unitários
Atenção: função só devolve valor em return
# coding: utf-8-*-...
def tests():
print("positive_root, 1.0:", positive_root(1,0,-1)) print("positive_root, 2.0:",positive_root(1,-2,0)) print("compute_pH, 3.111:",compute_pH(6.5e-5,0.01))
In : tests()
positive_root, 1.0: 1.0
positive_root, 2.0: 2.0
compute_pH, 3.111: 3.11104555393
■ O print escreve na consola mas a função tests() não devolveu valor (devolveu None).
Programação Estruturada
Erros
Erro de sintaxe
■ Erro na escrita do código que impede o interpretador de o executar
• Este é o mais fácil de corrigir, porque sabemos logo que o cometemos In : 6 * * 2
6 * * 2
^
SyntaxError: invalid syntax In : 8 + 5.3.2
8 + 5.3.2
^
SyntaxError: invalid syntax
■ Spyder analisa o código conforme o escrevemos e assinala a linha com o erro
Erros
Erro de execução (Exception)
■ O interpretador sabe o que deve fazer mas não consegue fazê-lo
• Estes erros só ocorrem durante a execução e podem ser mais difíceis de diagnosticar porque o erro pode estar noutra parte do código
In : y = z*2
y = z*2
NameError: name 'z' is not defined In : y = 0
In : z = 1 / y z = 1 / y
Erros
Erro lógico
■ O programa corre sem problemas mas não dá o resultado certo
• Estes erros tendem a ser os mais difíceis de corrigir
■ Exemplo: pH negativo?
def positive_root(a, b, c):
root = (-b + (b**2 - 4*a*c)**0.5)/(2*a) return root
def compute_pH(Ka, Ci):
H = positive_root(1, Ka, -Ka*Ci) return log10(H)
In : compute_pH(6.5e-5,0.01) Out: -3.11104555393015
Erros
Erro numérico
■ Devido à representação finita de números fraccionários, em 64 bits
• (Inteiros em Python 3.x usam o número de bits que for necessário)
• Obriga a arredondamentos e pode ser importante se o cálculo for iterado In : (2**0.5)**2
Out: 2.0000000000000004
■ Representação de números fraccionários em 64 bits:
• 1 bit para sinal, 11 para expoente (base 2) e 52 para fracção
Erros
Erro numérico
■ Informação sobre a representação em 64 bits In : import numpy In : info = numpy.finfo(float) In : info.bits Out: 64 In : info.eps Out: 2.2204460492503131e-16 In : info.tiny Out: 2.2250738585072014e-308 In : info.min Out: -1.7976931348623157e+308 In : info.max Out: 1.7976931348623157e+308 Informação:
Número de bits na representação Menor valor que somado a 1 dá >1
In : 1+info.eps-1
Out: 2.2204460492503131e-16
In : 1+0.5*info.eps-1
Out: 0.0
Mais pequeno com plena precisão
Menor valor representável
Programação Estruturada
Ciclo de vida
Ciclo de vida de um programa
■ Edição do código fonte
• Escrito, guardado em ficheiros .py
■ Interpretação do código fonte
• O interpretador traduz as instruções em instruções para o CPU
■ Execução
• O CPU executa o programa
■ Testar e avaliar o resultado
Ciclo de vida
Ciclo de vida de um programa, exemplo
■ Concebemos, implementámos e testámos o cálculo do pH
• A partir da concentração inicial e da constante de dissociação
Agora temos um novo problema
■ Qual o pH de 0.01g de ácido benzóico em 0.250 dm ?
• Calcular pH a partir da massa, volume e massa molecular
• Basta calcular a concentração e usar o que já temos
■ Solução: acrescentamos uma nova função
• Pensamos no algoritmo: calcular concentração
• Decompomos se necessário (não é; é simples)
• Implementamos e testamos
Ciclo de vida
Novo problema:
■ Qual o pH de 0.01g de ácido benzóico em 0.250 dm ?3
Solução:
■ Já temos praticamente tudo feito
■ Basta uma função que calcule a concentração
• Precisa da massa, massa molar e volume
■ E depois chamar a anterior com concentração e
K
adef compute_pH_mass(mass, mol_mass, volume, Ka):
"""
return pH value of a weak acid solution from mass of solute and volume of solution
"""
Ci = mass / mol_mass / volume return compute_pH(Ka,Ci)
Ciclo de vida
■ Uma maneira prática é acrescentar ao módulo phcalc
# coding: utf-8
-*-"""
Computes pH of an acid from concentration """
from numpy import log10
def positive_root(a, b, c):
...
def compute_pH(Ka, Ci):
...
def compute_pH_mass(mass, mol_mass, volume, Ka):
"""
return pH value of a weak acid solution from mass of solute and volume of solution
"""
Ci = mass / mol_mass / volume return compute_pH(Ka,Ci)
Ciclo de vida
Ciclo de vida de um programa, exemplo
■ Calcular o pH a partir da massa, volume e massa molecular:
def compute_pH_mass(mass, mol_mass, volume, Ka):
"""
return pH value of a weak acid solution from mass of solute and volume of solution
"""
Ci = mass / mol_mass / volume return compute_pH(Ka,Ci)
■ Testes:
def tests():
...
print("compute_pH, 3.111:",compute_pH(6.5e-5,0.01))
print("compute_pH_mass, 3.1111",compute_pH_mass(1.221, 122.1, 1, 6.5e-5)) print("compute_pH_mass, 3.000",compute_pH_mass(0.5, 122.1, 0.25, 6.5e-5))
Implementação
Estilo de Código
É importante escrever código legivel
■ Porque o código fonte serve:
• Para o interpretador executar
• Para humanos lerem
■ Código difícil de compreender
• É mais propenso a erros
• É mais difícil de corrigir, adaptar e melhorar
Estilo de Código
Nomes de variáveis
■ As variáveis devem ter nomes descritivos
• E.g. mass, mol_mass, volume
def compute_pH_mass(mass, mol_mass, volume, Ka):
Ci = mass / mol_mass / volume return compute_pH(Ka,Ci)
■ Excepto quando têm nomes convencionais
• E.g. a, b, c, Ci, pH, Ka
def positive_root(a, b, c):
root = (-b + (b**2 - 4*a*c)**0.5)/(2*a) return root
Estilo de Código
Nomes de funções
■ Devem descrever o que a função faz
■ Nomes compostos por várias palavras
• Convenção 1: usar letras maiúsculas e minúsculas phMassVol
• Convenção 2: (mais usado em Python) usar underscore compute_pH_mass
def compute_pH_mass(mass, mol_mass, volume, Ka):
Ci = mass / mol_mass / volume return compute_pH(Ka,Ci)
Estilo de Código
Inteligibilidade
■ Cada linha de código deve corresponder a um passo simples
■ Evitar linhas demasiado longas ou complexas
• Decompor em vários passos para ser mais inteligivel
■ Exemplo: menos claro
def compute_pH_mass(mass, mol_mass, volume, Ka):
return compute_pH(Ka, mass / mol_mass / volume)
■ Exemplo: mais claro
def compute_pH_mass(mass, mol_mass, volume, Ka):
Ci = mass / mol_mass / volume return compute_pH(Ka, Ci)
Estilo de Código
Documentação e comentários
■ Devemos documentar funções (e módulos)
■ Podemos também acrescentar comentários se for preciso
• Notas para o programador
def positive_root(a, b, c):
"return positive root of quadratic equation"
root = (-b + (b**2 - 4*a*c)**0.5)/(2*a) #This assumes a is greater than zero
return root
Mais informação:
■ https://www.python.org/dev/peps/pep-0008/ "PEP 8 -- Style Guide for Python Code"
Programação Estruturada
Implementação
Resumo
■ Programação estruturada: funções
■ Testes unitários
■ Tipos de erro: sintaxe, exception, lógicos e numéricos
■ Ciclo de vida e reutilização de código
■ Estilo de código
Leitura adicional:
■ Recomendada: Capítulo 4 dos apontamentos