1
Implementação
Resumo■ Exemplo: cálculo de pH, agora estruturado■ Testes unitários■ Ciclo de vida de um programa■ Erros e precisão numérica
3
Calcular pH
Solução de ácido benzóicoC 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 ×Ka 10−5
= = ⇔ + x − = 0Ka
[ ][ CO ]H+ C6H5 O−
[ COOH]C6H5
x2
− xCi
x2 Ka CiKa
x =−b± −4acb2√
2a
pH = −lo ([ ]) = −lo (x)g10 H+ g10
4
Calcular 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 """
5
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 osobjectos.
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) return positive root of quadratic equation
6
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) return pH value for weak acid from dissociation constant and initial concentration ...
7
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 Out: 2.0
8
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 importarlog10
• É 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): ... def compute_pH(Ka, Ci): ...
9
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) Out: 3.11104555393015
10
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.• Sempre que alteramos alguma função.
11
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 compute_pH, 3.111: 3.11104555393
12
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') positive_root, 1.0: 1.0 positive_root, 2.0: 2.0 compute_pH, 3.111: 3.11104555393
13
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 precisamde criar novamente os objectos das funções alteradas
■ Para isso o interpretador tem de reler a assinatura da função e todaa "receita" no corpo da função
■ A forma mais prática de fazer isso no Spyder é F5• (Grava e corre todo o ficheiro)
■ O Spyder automaticamente recarrega outros módulos
14
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()
15
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 devolveuvalor (devolveu None).
17
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 linhacom o erro
18
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 ZeroDivisionError: division by zero
19
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
(Falta o sinal negativo no cálculo do logaritmo)
20
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• Equivale a cerca de 17 algarismos significativos em decimal
21
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 Maior valor representável
23
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• e voltar à edição as vezes que for preciso...
24
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
3
25
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 Ka
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)
26
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) def tests(): ...
27
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))
29
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• (prejudica a avaliação em ICE)
30
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
■ Importante: que seja fácil perceber o que representa
31
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)
32
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)
33
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"
35
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ódigoLeitura adicional:■ Recomendada: Capítulo 4 dos apontamentos■ Opcional: Livro, Cap. 16 até pag. 479 e Cap. 17 até pag. 491.