Instruções: Enquanto você lê, você deve acompanhar os exemplos utilizando um console e Python. Para essa unidade, você deve ler a seção 4.6 sobre funções do tutorial Python. Discutiremos alguns exemplos sobre a importância de funções e sobre como utilizá-las efetivamente. Depois, você deverá ler sobra as demais funcionalidades de função em Python (do início da seção 4.7 até 4.7.3). Show Vamos retomar o exemplo de nossa calculadora, mas agora vamos adicionar outras operações operações. while True: operador = input() if operador == "+": num1 = float(input()) num2 = float(input()) soma = num1 + num2 print(soma) elif operador == "-": num1 = float(input()) num2 = float(input()) diferenca = num1 - num2 print(diferenca) elif operador == "*": num1 = float(input()) num2 = float(input()) produto = num1 * num2 print(produto) elif operador == "/": num1 = float(input()) num2 = float(input()) if num2 == 0: print("Divisor não pode ser zero") continue produto = num1 / num2 print(produto) elif operador == "F": break else: print("Operação inválida")Observe que usamos um comando continue. Essa instrução termina a iteração atual do laço, mas ao contrário de break, continua na próxima iteração. Assim, imediatamente após executar continue, o interpretador irá voltar a verificar a condição do while. Perceba que o programa começa a ficar muito grande e já não cabe em muitas telas, mas ainda é razoavelmente simples e a maioria dos programadores não teria dificuldades em ler esse código. No entanto, à medida que adicionamos operações, a situação fica um pouco mais complicada. Queremos que nossa calculadora seja mais útil do que as calculadoras de mesa tradicionais, então vamos adicionar duas operações, uma menos trivial do que a outra: a raiz quadrada e as raízes de uma equação do segundo grau. Para calcular a raiz quadrada, vamos de novo importar um módulo math, então adicionamos no início no arquivo import mathCom isso, basta basta escolher um nome para a operação apropriado e adicionar mais algumas linhas no corpo do while. # .... elif operador == "sqrt": num = float(input()) raiz = math.sqrt(num) print(raiz) # ...A segunda operação — encontrar as raízes de uma equação do segundo grau — diverge das operações anteriores, pois não há função prontamente disponível em Python, e precisamos de um número de instruções um pouco maior. Primeiro, lembramos que uma equação do segundo grau é escrita como $$ ax^2 + bx + c = 0 $$ Desde muito sabemos como encontrar o valor de $x$. Primeiro, calculamos o valor do discriminante $$ \Delta = b^2 - 4ac $$ Com o valor de $\Delta$, podemos determinar quantas e quais são as soluções da equação. A fórmula para isso é $$ x = \frac{-b \pm \sqrt{\Delta}}{2a} $$ que é popularmente conhecida como fórmula de Bhaskara. Adicionamos o seguinte ao corpo da função while True: # .... elif operador == "bhaskara": a = float(input()) b = float(input()) c = float(input()) delta = b*b - 4 * a * c if delta < 0: print("Não há raízes reais") continue elif delta == 0: print("Há uma raiz distinta apenas") else: print("Há duas raízes distintas") x1 = (-b + math.sqrt(delta)) / (2 * a) x1 = (-b - math.sqrt(delta)) / (2 * a) print(f"As raízes são {x1} e {x2}") # ...Se tentarmos olhar para todo o programa, teremos uma surpresa: while True: operador = input() if operador == "+": num1 = float(input()) num2 = float(input()) soma = num1 + num2 print(soma) elif operador == "-": num1 = float(input()) num2 = float(input()) diferenca = num1 - num2 print(diferenca) elif operador == "*": num1 = float(input()) num2 = float(input()) produto = num1 * num2 print(produto) elif operador == "/": num1 = float(input()) num2 = float(input()) if num2 == 0: print("Divisor não pode ser zero") continue produto = num1 / num2 print(produto) elif operador == "sqrt": num = float(input()) raiz = math.sqrt(num) print(raiz) elif operador == "bhaskara": a = float(input()) b = float(input()) c = float(input()) delta = b * b - 4 * a * c if delta < 0: print("Não há raízes reais") continue elif delta == 0: print("Há uma raiz distinta apenas") else: print("Há duas raízes distintas") x1 = (-b + math.sqrt(delta)) / (2 * a) x1 = (-b - math.sqrt(delta)) / (2 * a) print(f"As raízes são {x1} e {x2}") elif operador == "F": break else: print("Operação inválida")Dessa vez, tenho certeza de que todo o código não cabe em uma única tela de seu editor. Mais importante do que isso, é extremamente difícil ler esse código e entender o que está acontecendo! O motivo, além do tamanho, é que para entender o código acima precisamos nos preocupar, ao mesmo tempo, com diversos problemas distintos:
Como estamos fazendo tudo isso de maneira intercalada, ao lermos esse código, ora nos preocumos com o while, ora com a entrada e saída, ora com a fórmula de Bhaskara. Pior! Se houver um erro na lógica do nosso programa, vamos gastar bastante tempo tentando descobrir onde ele está. FunçõesPara resolver esse problema, vamos criar uma abstração para conjunto de instruções dedicadas a resolver uma determinada tarefa. Vamos reescrever ou refatorar o programa. Vamos adotar uma estratégia de resolver os problemas mais gerais primeiro, e depois os mais específicos (algumas pessoas chamam a isso de estratégia top-down). Primeiro precisamos descobrir qual a tarefa principal do programa, isso é, qual é o conjunto de instruções deve ser executado ao iniciar o programa. Nesse exemplo, a primeira instrução é while, que é responsável pelo controle das operações digitadas pelo usuário. Vamos então nos concentrar nesse problema e esconder todos os demais. Escrevemos o seguinte: while True: operador = input() if operador == "+": operacao_soma() elif operador == "-": operacao_diferenca() elif operador == "*": operacao_multiplicacao() elif operador == "/": operacao_divisao() elif operador == "sqrt": operacao_raiz() elif operador == "bhaskara": operacao_bhaskara() elif operador == "F": break else: print("Operação inválida")Agora fica muito mais simples entender o que esse código faz: ele lê uma linha do teclado e realiza uma operação de acordo com o que o usuário digitar. É claro que operacao_soma, operacao_diferenca etc. não são instruções da linguagem Python. Cada um desses nomes é uma abstração: aqui, abstrair significa esconder os detalhes de como realizar um determinada operação. Se tentarmos executar um código assim iríamos obter uma mensagem de erro do interpretador dizendo que algum nome não está definido, como se ele reclamasse, "não sei do que você está falando". Vamos então definir cada um desses nomes. Para isso, antes do while, adicione def operacao_soma(): pass def operacao_diferenca(): pass def operacao_produto(): pass def operacao_divisao(): pass def operacao_raiz(): pass def operacao_bhaskara(): passO que estamos fazendo é definir diversas novas funções. Uma função é um conjunto de instruções realiza determinada tarefa identificada por um nome. As funções acima são só stubs, que são funções incompletas. O motivo para criar stub é poder testar o código do programa principal enquanto ainda estamos desenvolvendo nosso programa. Depois de terminado o trecho de código do programa principal, devemos implementar cada uma das funções definidas acima. Implementar uma função significa escrever o conjunto de instruções que realiza a tarefa correspondente. Uma das vantagens de definir e usar funções é que é muito mais fácil implementar tarefas pequenas, uma de cada vez, do que tentar resolver todo o problema de uma vez. Para a função operacao_soma basta copiar as instruções do código original: def operacao_soma(): num1 = float(input()) num2 = float(input()) soma = num1 + num2 print(soma)Você deve fazer o mesmo para as operações de diferença e produto. Para a operação de divisão, no entanto, precisamos tomar um cuidado a mais. Copiando o código original, teríamos: def operacao_divisao(): num1 = float(input()) num2 = float(input()) if num2 == 0: print("Divisor não pode ser zero") continue produto = num1 / num2 print(produto)O problema do código acima é que a instrução continue só faz sentido dentre do corpo de um comando for ou while. O que queremos, nesse caso, é terminar a função e retornar ao menu. Para isso, existe a instrução return que termina a função que a contém e continua a execução imediatamente depois do ponto de chamada. def operacao_divisao(): num1 = float(input()) num2 = float(input()) if num2 == 0: print("Divisor não pode ser zero") return produto = num1 / num2 print(produto)Faça o mesmo para operação para obter raízes da equação. Passando argumentos e devolvendo valoresVamos olhar como ficaria a função para a operação da raiz: def operacao_raiz(): num = float(input()) raiz = math.sqrt(num) print(raiz)A expressão math.sqrt(num) é uma chamada a uma função de nome sqrt importada com o módulo math. Repare que o radiando é passado como parâmetro da função é a raiz é obtida, assim, a função sqrt funciona, de fato, como uma função matemática, que tem uma entrada e devolve uma saída. Embora calcular a raiz quadrada já é uma função própria de uma biblioteca padrão do Python, ela normalmente não é uma instrução elementar dos nossos computadores modernos. Como bons computeiros, devemos ter o espírito curioso de descobrir como uma tal operação complexa pode ser computada a partir de operações elementares (soma, subtração, multiplicação e divisão). Há vários métodos conhecidos e você deve ter já estudado pelo menos um na escola. Nesse exemplo, vamos usar o método de Newton. Como essa é uma operação complicada, primeiro vamos criar um stub para poder modificar o código de operacao_raiz. Fazemos o seguinte: def minha_sqrt(radiando): raiz = radiando / 2 # TODO: implementar método de Newton return raiz # ... def operacao_raiz(): num = float(input()) raiz = minha_sqrt(num) print(raiz)A função minha_sqrt é só um stub, mas ela ilustra dois conceitos novos:
Agora que já organizamos nosso programa em funções, podemos nos concentrar em implementar o método de Newton. Observe que fizemos um comentário que começa com # TODO: .... É comum criar comentários como esse ou # FIXME: .... para indicar trechos de código que merecem atenção posterior. Outra maneira, para projetos grandes, é anotar um bug num gerenciador de tarefas (como bugzilla, issues, etc.). Revise o método de Newton e implemente a função (você pode achar mais fácil resolver o exercício correspondente). Repetição de códigoVamos resolver mais um exercício:
Antes de começar, vamos listar as duas pequenas tarefas que devemos fazer:
Novamente, precisamos identificar qual delas é a tarefa principal, isso é, que será executada primeiro quando o programa começar. Em grande parte de nossos problemas essa vai ser sempre a tarefa de ler os dados de entrada, realizar algumas operações e mostrar os dados de saída. Nesse caso, vamos fazer o seguinte:: def main(): n = int(input("Digite um número inteiro: ")) if eh_produto_dois_primos(n): print(f"O número {n} é produto de dois primos.") else: print(f"O número {n} não é produto de dois primos.") main()Observe que definimos uma função chamada main e que adicionamos uma chamada a essa função na última linha do programa, que é a única instrução do programa que não está dentro de uma função. Sempre que nosso problema não for trivial, vamos preferir organizar nossos programas dessa maneira. O nome main é uma convenção e serve para identificar facilmente qual é a função principal do programa. Para entender esse programa, precisamos investigar a linha que contém if eh_produto_dois_primos(n):. O que está implícito aqui é que eh_produto_dois_primos é uma função que devolve um valor booleano dependendo se o número n passado como parâmetro é produto de dois primos. Para podermos deduzir isso foi fundamental que o nome da função fosse bem representativo do que ela faz e do que ela devolve. Para poder testar a função main, faremos um stub. def eh_produto_dois_primos(n): """Devolve True se o argumento n puder ser escrito como um produto de dois primos""" return TrueA novidade nesse programa é que adicionamos uma string na primeira linha da função. Essa string não é associada a nenhuma variável e não tem nenhum efeito. O motivo de adicionarmos é que ela serve para documentar o que a função faz. Por mais que uma função tenha (e deva ter) um bom nome, nem sempre é claro o que cada função faz, particularmente se voltarmos a ler nosso código depois de alguns dias. Essas strings são denominadas strings de documentação ou documentation strings. Quando testarmos o programa com qualquer número, digamos, 100, obteremos sempre uma mensagem como O número 100 é produto de dois primos.independentemente do número lido. Para testar o conjunto de instruções correspondentes ao else da função main podemos mudar o nosso stub. Mas agora, já podemos implementar a função de fato. Antes, vamos escrever um algoritmo, em bem alto nível.
Você pode só acreditar que esse algoritmo está correto, ou pode tentar se convencer de que está. Um bom computeiro tenta entender bem o que um algoritmo faz antes de tentar codificá-lo. Para isso, teste com algumas instâncias pequenas utilizando lápis e papel. Quando estivermos confiantes de que o o algoritmo está correto, podemos passar à implementação. def eh_produto_dois_primos(n): """Devolve True se o argumento n puder ser escrito como um produto de dois primos""" produto_primos = False for q in range(1, n + 1): if n % q == 0: r = n // q r_eh_primo = True for d in range(2, r): if r % d == 0: r_eh_primo = False break q_eh_primo = True for d in range(2, q): if r % d == 0: q_eh_primo = False break if r_eh_primo and q_eh_primo: produto_primos = True break return produto_primosLeia com atenção e copie essa função no seu programa. Podemos testar o programa digitando o número 15 que é um produto de dois primos 3 e 5. Ao verificar a saída iremos ver que o programa imprimiu corretamente O número 15 é produto de dois primos.Se testarmos com outros 10 ou 4, o programa também responderá corretamente. Mas devolver a resposta correta para alguns exemplos não significa que o programa está correto. Na verdade, não importa qual número inteiro fornecermos, o programa sempre responderá que ele é produto de dois primos. Isso não é verdade quando a resposta é não, como para o número 8 ou o número 20. Concluímos duas coisas: primeiro, nosso programa está errado, segundo, é importante testar nossos programas com vários exemplos de entrada, particularmente para entradas que fornecem saídas diferentes. Para descobrir onde está o erro do programa, podemos usar diversas estratégias, como simulá-lo com um debugger, ou ler o código lentamente com atenção. Se você já não descobriu o erro, pare um pouco e tendo descobri-lo. Uma vez descoberto o erro, precisamos corrigi-lo. Mais importante, precisamos entender porque esse erro ocorreu para início de conversa. O erro é um erro de digitação na segunda ocorrência de if r % d == 0: que deveria ser if q % d == 0:. Como você deve adivinhar, isso ocorreu porque temos dois trechos de código muito parecidos e o segundo foi obtido copiando e modificando o primeiro, mas esquecemos de modificar uma linha. Esse exemplo descreve o que chamamos de duplicidade de código, que é uma situação extremamente comum no desenvolvimento de software. Muitas vezes, cada trecho de código tem papeis similares, mas para parâmetros diferentes. No exemplo acima, queremos decidir se um dado número (r ou q) é primo. Em situações como essa, devemos refatorar o código e definirmos uma função. Reescrevemos assim. def eh_primo(n): """Verifica se n é primo""" eh_primo = True for d in range(2, n): if n % d == 0: eh_primo = False break return eh_primo def eh_produto_dois_primos(n): """Devolve True se o argumento n puder ser escrito como um produto de dois primos""" produto_primos = False for q in range(1, n + 1): if n % q == 0: r = n // q if eh_primo(r) and eh_primo(q): produto_primos = True break return produto_primosDeve ser evidente que a nova versão é mais simples e muito mais compacta. Dessa vez, não há repetição de código. Testando para o número 8 vemos que, agora sim, ele responde corretamente que não é produto de dois primos. Infelizmente, como você já pode desconfiar, esse programa não está correto. Estude o programa e tente determinar para que exemplos esse programa devolve a saída incorreta! Depois, corrija seu programa. Ao terminar esse exercício você vai descobri mais uma vantagem de ter refatorado o programa com uma nova função, ao invés de manter as duas cópias praticamente idênticas de um conjunto de instruções. Criando e organizando seu programaVamos resolver mais um problema para nos exercitar.
Você deve tentar fazer todo esse programa por conta própria. Para isso, procure seguir a mesma estratégia que seguimos antes:
Em seguida eu mostro como eu resolveria esse problema. Não leia esse código enquanto não tiver terminado o seu próprio programa. Seu programa pode divergir completamente do código abaixo, mas isso não significa que uma maneira é mais correta do que a outra. Procure analisar criticamente as diferenças. Uma peculiaridade da Computação é que, embora seja uma ciência dita exata, os algoritmos são tão distintos quanto seus próprios programadores. Tanto que alguns diriam que programação é uma arte! """ Calcula as médias finais dos estudantes. Entrada: - uma linha com o número n de estudantes - n linhas correspondentes às notas de provas - n linhas correspondentes às notas de exercícios Saída: - n linhas correspondentes às medias finais """ import math def ler_lista_numeros(n): """Lê uma lista de n números do teclado""" lista = [] for _ in range(n): numero = float(input()) lista.append(numero) return lista def imprimir_lista_numeros(lista): """Imprime cada número de lista em um linha, com duas casas decimais""" for valor in lista: print(f"{valor:.2f}") def obter_maximo(lista): """Devolve o valor máximo da lista""" assert lista, "Lista não pode ser vazia" maximo = lista[0] for numero in lista: if numero > maximo: maximo = numero return maximo def criar_lista_normalizada(lista_antiga): """Devolve uma nova lista com os elementos de lista_antiga normalizados pelo máximo""" maximo = obter_maximo(lista_antiga) lista_nova = [] for valor in lista_antiga: novo_valor = valor / maximo lista_nova.append(novo_valor) return lista_nova def calcular_medias_finais(notas_provas, notas_exercicios): """Devolve uma nova lista com as médias geométricas dos elementos de notas_provas e notas_exercicios""" medias_finais = [] assert len(notas_provas) == len(notas_exercicios), \ "As listas de notas devem ter o mesmo tamanho" n = len(notas_provas) for i in range(n): media_final = math.sqrt(notas_provas[i] * notas_exercicios[i]) medias_finais.append(media_final) return medias_finais def main(): n = int(input(n)) notas_provas = ler_lista_numeros(n) notas_exercicios = ler_lista_numeros(n) notas_provas = criar_lista_normalizada(notas_provas) notas_exercicios = criar_lista_normalizada(notas_exercicios) medias_finais = calcular_medias_finais(notas_provas, notas_exercicios) imprimir_lista_numeros(medias_finais) main()Há vários detalhe nesse programas e talvez alguns sejam novos para você. Você deve pesquisar as instruções que não conhecer e descobrir o objetivo de elas estarem ali. O que queremos destacar nesse exemplo, no entanto, é a forma como está organizado. É uma boa prática (embora nem sempre seja seguida no mercado) criar programas bem documentados e com formatação padronizada, como acima. O programa acima está organizado de acordo com algumas convenções:
Escopo de variáveis e ciclo de vida de funçõesQuando aprendemos a instrução de atribuição, vimos que ela tem a seguinte forma <nome de variável> = <expressão que computa um valor na memória>Assim, o nome do lado esquerdo deve ser um nome que referencia algum valor armazenado na memória. Essa associação, no jargão de Python é chamada de binding. Uma vez definido o binding, podemos usar o nome da variável em um expressão para representar o valor referenciado. O nome de uma variável, no entanto, só pode ser usado quando satisfeitas determinadas condições:
Vamos estudar dois tipos de escopo.
Para deixar esses conceitos um pouco mais concretos, vejamos um exemplo de código: Existe uma única variável global denominada PI. Essa variável pode ser utilizada em qualquer ponto do programa que execute após sua definição. Observe quem ambas as funções calcular_area_disco e calcular_area_disco fazem uso de PI. As variáveis locais de calcular_area_disco são o parâmetro raio e as demais variáveis raio_quadrado e area. Analogamente, as variáveis locais de calcular_volume_esfera são raio, raio_cubo e volume. Finalmente, as variáveis locais de main são raio, peso, volume e densidade. Cada função enxerga apenas as variáveis globais e suas variáveis locais! Assim, o nome raio_cubo não está no escopo de main nem de calcular_area_disco. Mas e os nomes de variáveis que são comuns a mais de uma função? Cada função têm suas próprias variáveis locais, assim há três nomes raio distintos. Você pode pensar que há o raio-da-função-calcular_area_disco, o raio-da-função-calcular_volume_esfera e o raio-da-função-main. O mesmo acontece para a variável volume que é comum a calcular_volume_esfera e main. Para entender melhor, façamos um exercício. O que será impresso pelo programa a seguir: INCREMENTO = 3 def somar(x): x = x + INCREMENTO def main(): x = 10 somar(x) print(x) main()Você deve ter respondido correto: 10. Embora ambas funções somar e main tenham uma variável de nome x, elas são nomes que se referem a variáveis distintas. Fazemos uma modificação. O que será impresso? INCREMENTO = 3 def somar(x): x = x + INCREMENTO return x def main(): x = 10 soma1 = somar(x) INCREMENTO = 4 soma2 = somar(x) print(soma1) print(soma2) main()Agora o valor calculado pela primeira chamada de somar foi devolvido e armazenado em um variável referenciada por soma1 e o valor calculado pela segunda chamada em soma2. Nesse caso, não é tão simples descobrir o que será impresso. Se você simular esse programa, obterá 13 e 13. Por que não foi impresso 13 e 14? Sabemos que somar depende da variável global INCREMENTO, mas a função main faz uma atribuição a uma variável local INCREMENTO. Lembrem-se de que atribuições feitas em funções são sempre locais! Por esse motivo (e por alguns outros que você ainda vai descobrir), nunca use ou faça modificações em variáveis globais! Repetindo: nunca! A única razão para usarmos uma variável global é para dar um nome a um valor que nunca deverá ser mudado durante a execução do algoritmo. Chamamos essas variáveis de constantes. Aliás, é por esse motivo que convencionamos escrever todas as variáveis globais em maiúsculas, para indicar que elas são constantes e não devem ser alteradas. Para descobrir o que está acontecendo internamente no interpretador Python, precisamos entender o ciclo de vida de uma função. Para isso, vamos fazer mais um exercício.
Experimente resolver esse exercício. Para os impacientes, segue o código que eu faria: NOTA_MINIMA = 5.0 def obter_maximo(lista): assert lista, "Lista não pode ser vazia" maximo = lista[0] for valor in lista: if maximo < valor: maximo = valor # breakpoint() return maximo def multiplicar_fator(lista, fator): n = len(lista) for i in range(n): lista[i] = lista[i] * fator def ler_lista_notas(): n = int(input("Digite o número de estudantes: ")) lista_notas = [] for _ in range(n): lista_notas.append(float(input())) return lista_notas def imprimir_lista_aprovacao(lista_notas): for nota in lista_notas: if nota < NOTA_MINIMA: print("reprovado") else: print("aprovado") def main(): lista_notas = ler_lista_notas() maximo = obter_maximo(lista_notas) fator = 10.0 / maximo multiplicar_fator(lista_notas, fator) imprimir_lista_aprovacao(lista_notas) main()Vamos fazer um desenho representando a memória do computador no momento imediatamente anterior em que obter_maximo devolve o valor máximo da lista. Há varias coisas a se notar. Nesse momento, a função ler_lista_notas já foi executada e terminada, então todas as variáveis locais dessa função já não estão mais disponíveis na função, isso é, não há escopo para as variáveis dessa função. Do mesmo modo, a função multiplicar_fator a função imprimir_lista_aprovacao também não foram chamadas ainda, e portanto suas variáveis ainda não foram criadas. Há exatamente duas funções sendo executadas nesse momento: a função main e a função obter_maximo, então existem exatamente dois escopos de função, além do escopo global. Opcional: Se você quiser verificar isso, faça o seguinte: descomente a linha com breakpoint() na função obter_maximo() e execute o seu programa. Você entrará no mode de debug com uma mensagem mostrando a próxima instrução. Digite bt (de backtrace) para mostrar a trajetória do seu programa até essa instrução e explore investigando os valores das variáveis, e.g., digite maximo para ver o valor associado a esse nome no escopo atual. Se você não gosta de usar o terminal, então pode fazer o mesmo configurando sua IDE preferida e adicionado um breakpoint na linha correspondente ao return. Digite o número de estudantes: 4 4.8 3.5 8.0 7.5 > /home/user/ra123456/funcoes/notas.py(10)obter_maximo() -> return maximo (Pdb) bt /home/user/ra123456/funcoes/notas.py(40)<module>() -> main() /home/user/ra123456/funcoes/notas.py(34)main() -> maximo = obter_maximo(lista_notas) > /home/user/ra123456/funcoes/notas.py(10)obter_maximo() -> return maximo (Pdb) valor 7.5 (Pdb) maximo 8.0 (Pdb) continue aprovado reprovado aprovado aprovadoImediatamente depois que a função obter_maximo termina, o valor referenciado na frente de return é devolvido para a função main. Nesse momento, o escopo da função obter_máximo é destruído. A próxima instrução de main é uma atribuição ao nome de variável máximo, que recebe o valor devolvido. Podemos olhar para a seguinte figura: Com isso, podemos resumir o ciclo de vida de uma função:
O mecanismo de chamadas de função em Python tem uma consequência especial para listas passadas como argumentos. Quando alteramos uma lista passada por argumento, essas mudanças ficarão visíveis para a função que realizou a chamada. Isso acontece quando chamamos a função multiplicar_fator. Para ver o motivo disso, repare que tanto a variável lista_notas de main quanto a variável lista de multiplicar_fator são associadas à mesma lista. O desenho a seguir representa a memória imediatamente após a primeira iteração da linha lista[i] = lista[i] * fator. MódulosObservação: Depois de ler o texto a seguir, procure ler e estudar a seção 6 do tutorial Python. À medida em que nossos projetos ficam maiores e mais complexos, copiar e colar um conjunto de funções em nossos arquivos Python torna-se bastante difícil. Mais dos que isso, pode ser que um conjunto de funções possa ser útil a diferentes programas. A maneira de tratar isso no universo Python é criando-se módulos, que agrupam um conjunto de funções e variáveis relacionadas. Um módulo é um arquivo Python que é executado quando executamos o comando import. Os módulos podem vir de várias partes, dependendo de como foram instalados:
Por enquanto, só iremos falar em como criar um módulo pessoal. Para isso, vamos ver um exemplo.
Isso é bem parecido com o que já fizemos, então nada melhor do que copiar e colar algumas funções auxiliares. Copiamos ler_lista_numeros e imprimir_lista_numeros. Para calcular as médias, já temos uma função que faz isso, calcular_medias_finais, mas melhor ajustar os nomes, para não nos confundirmos. Com isso, escrevemos um programa chamado notas_parciais.py. import math def ler_lista_numeros(n): """Lê uma lista de n números do teclado""" lista = [] for _ in range(n): numero = float(input()) lista.append(numero) return lista def imprimir_lista_numeros(lista): """Imprime cada número de lista em um linha, com duas casas decimais""" for valor in lista: print(f"{valor:.2f}") def calcular_medias_geometricas(lista1, lista2): """Devolve uma nova lista com as médias geométricas dos elementos de lista1 e lista2""" medias_geometricas = [] assert len(lista1) == len(lista2), "As listas de devem ter o mesmo tamanho" n = len(lista1) for i in range(n): media_geometrica = math.sqrt(lista1[i] * lista2[i]) medias_geometricas.append(media_geometrica) return medias_geometricas def main(): n = int(input()) notas_exercicios1 = ler_lista_numeros(n) notas_exercicios2 = ler_lista_numeros(n) medias_parciais = calcular_medias_geometricas(notas_exercicios1, notas_exercicios2) imprimir_lista_numeros(medias_parciais) main()Repare que, enquanto nesse programa resolvemos o problema de repetição de código, afinal, só definimos ler_lista_numeros uma vez, temos que escrever exatamente as mesmas instruções que estavam em outro programa anterior. Nessas situações, é mais conveniente criar um módulo que possa ser compartilhado entre os dois programas. Primeiro, criamos um arquivo, no mesmo diretório, chamado utilidades.py e movemos as funções utilitárias para lá: import math def ler_lista_numeros(n): """Lê uma lista de n números do teclado""" lista = [] for _ in range(n): numero = float(input()) lista.append(numero) return lista def imprimir_lista_numeros(lista): """Imprime cada número de lista em um linha, com duas casas decimais""" for valor in lista: print(f"{valor:.2f}") def calcular_medias_geometricas(lista1, lista2): """Devolve uma nova lista com as médias geométricas dos elementos de lista1 e lista2""" medias_geometricas = [] assert len(lista1) == len(lista2), "As listas de devem ter o mesmo tamanho" n = len(lista1) for i in range(n): media_geometrica = math.sqrt(lista1[i] * lista2[i]) medias_geometricas.append(media_geometrica) return medias_geometricasCom isso podemos modificar nosso arquivo notas_parciais.py para conter apenas: from utilidades import ler_lista_numeros, imprimir_lista_numeros, calcular_medias_geometricas def main(): n = int(input()) notas_exercicios1 = ler_lista_numeros(n) notas_exercicios2 = ler_lista_numeros(n) medias_parciais = calcular_medias_geometricas(notas_exercicios1, notas_exercicios2) imprimir_lista_numeros(medias_parciais) main()A primeira linha desse programa faz o seguinte:
Agora, essa as funções podem ser utilizadas em vários programas, mas sem a necessidade de copiar e colar. Por exemplo, suponha que, no final do semestre, tenhamos que escrever outro programa.
Podemos, agora escrever o seguinte programa, notas_finais.py import utilidades def calcular_medias_aritmeticas(lista1, lista2): """Devolve uma nova lista com as médias aritméticas dos elementos de lista1 e lista2""" medias_aritmeticas = [] assert len(lista1) == len(lista2), "As listas de devem ter o mesmo tamanho" n = len(lista1) for i in range(n): media_aritmetica = (lista1[i] + lista2[i]) / 2 medias_aritmeticas.append(media_aritmetica) return medias_aritmeticas def main(): n = int(input()) notas_exercicios1 = utilidades.ler_lista_numeros(n) notas_exercicios2 = utilidades.ler_lista_numeros(n) medias_parciais = calcular_medias_aritmeticas(notas_exercicios1, notas_exercicios2) utilidades.imprimir_lista_numeros(medias_parciais) main()Observe que agora utilizamos uma sintaxe alternativa para importar o módulo. O módulo utilidades.py continua sendo executado e importado como antes. A única diferença é que o nome da função ler_lista_numeros e demais não estarão disponíveis no escopo global do nosso programa. Ao invés disso, estará disponível o nome utilidades, por onde acessamos as funções do módulo. Finalmente, repare que a função auxiliar calcular_medias_aritmeticas que tivemos que criar é suficientemente genérica e pode ser que ela seja útil em outro programa. Assim, pode ser razoável movê-la para o módulo de utilidades. Faça isso como exercício. A decisão de quando uma função deve estar disponível em um módulo para reuso ou mesmo como organizar os módulos não é trivial. Só com a experiência você ganhará mais confiança para essa tarefa. Page 2
Professor: Lehilton Pedrosa
Últimos avisos
Vamos conversar um pouquinho sobre como os computadores modernos funcionam. Depois vamos discutir sobre o que se trata de fato em Computação, entender que tipo de problemas os computadores resolvem e o que são esses animais chamados algoritmos.
Agora que já sabemos o que é um algoritmo, temos que descobrir como escrever um algoritmo. Queremos descobrir quais são os blocos fundamentais que os compõem.
Enquanto os algoritmos são úteis por si só, queremos executá-lo em um computador. Como um computador não entende português, vamos temos que escrever nossos algoritmos em uma linguagem de programação.
Agora que já conhecemos os principais conceitos de algoritmos e linguagem de programação, vamos materializá-los em Python. Veremos tipos básicos de variáveis e a sintaxe de expressões aritméticas. Depois, veremos como escrever um programa com entrada e saída. Finalmente, vamos criar programas com uma estrutura condicional.
Um dos motivos para se utilizar computadores é que queremos tratar grandes quantidades de dados. Além disso, queremos escrever algoritmos genéricos que atuem da mesma maneira em cada elemento desses conjuntos de dados. Para isso, utilizamos as chamadas listas em Python.
À medida em que os programas ficam mais complicados, mais difícil fica controlar e entender o conjunto das instruções. Por isso, é importante criar abstrações sobre um conjunto de instruções que tenham determinado significado; a essa abstração damos o nome de função.
Vamos mergulhar um pouco mais fundo ao escrever algoritmos baseados em comandos iterativos.
Por enquanto, aprendemos apenas a manipular listas. Para muitas aplicações, essas listas contém apenas elementos escalares, como números ou strings, mas elas podem ter estruturas mais elaboradas. Vamos aprender a manipular matrizes e aproveitar para falar um pouco de arquivos.
Até agora guardamos dados como variáveis ou listas de variáveis simples. Vamos aprender novos tipos de coleção de dados e estudar como representar dados na memória.
Muitas vezes não é suficiente escrever e implementar um algoritmo. Para de fato resolver um problema, precisamos estimar a quantidade de recursos que nossos algoritmos gastam e garantir que eles executam em um tempo razoável.
Até agora vimos algoritmos iterativos, isso é, tudo que os eles fazem é executar um conjunto de instruções até que uma condição seja satisfeita. Aprenderemos uma nova maneira de pensar e escrever algoritmos. Vamos descobrir que podemos escrever algoritmos ora muito mais simples, ora muito mais sofisticados.
Você deve aprender a utilizar um terminal de comandos, realizar operações básicas no controle de versão Git e submeter uma tarefa na disciplina.
Você deve definir um problema computacional e descrever um algoritmo para resolver esse problema.
Você deve descrever um algoritmo para resolver um problema dado utilizando diferentes estruturas de controle.
Você deve ler uma sequência de instruções para um problema dado e analisá-la, à luz do seu conhecimento de algoritmos.
Construa um primeiro programa em Python utilizando estruturas condicionais.
Você deve utilizar instruções em Python para manipular listas.
Você deve utilizar instruções em Python para percorrer listas e acumular seus valores.
Você deve deve definir suas próprias funções agregadoras para listas.
Você deve deve determinar se umx estudante está apovadx na disciplina. Para isso, deverá criar um programa usando funções organizadamente.
Você deve manipular matrizes para tratar imagens binárias e deverá manipular arquivos para armazená-las.
Você deve utilizar tuplas e dicionários para extrair medidas de um texto e implementar uma função de auto complemento.
Você deve criar um aplicativo de agenda em modo texto. Para isso, deverá determinar como representar uma coleção de dados dinâmica na memório usando listas e memória.
Para resolver os problemas dessa tarefa em tempo aceitável, você precisará pensar em algoritmos eficientes.
Não há como aprender recursão sem praticar bastante.
Para entender recursão você deve aprender recursão. Links recomendados para a disciplina:
Alguns links úteis, mas cujo uso não é recomendado para a disciplina: |