#!/usr/bin/env python # coding: utf-8 # ##
Introdução à Lógica de Programação
# ###
Um curso prático para estudantes das Ciências da Vida
# --- # ##
Aula 2. Funções
# #####
Instrutor: Pedro C. de Siracusa
# Funções são como liquidificadores! Inserimos bananas e um copo de leite, ajustamos alguns parâmetros como o tempo de processamento e por fim recebemos uma deliciosa vitamina! E o melhor é que não precisamos entender os mínimos detalhes sobre como os mecanismos internos do aparelho funcionam para fazer nossa vitamina. Tampouco precisamos ter construído o liquidificador para poder saboreá-la. Além disso, qualquer que fosse o liquidificador, esperaríamos obter algo não muito diferente de uma vitamina, desde que utilizássemos os mesmos ingredientes e regulagens. # # # Agora de forma mais sofisticada, funções são construções que **encapsulam** um determinado comportamento que se espera executar múltiplas vezes durante a execução de um programa. São rotinas, que podem ter sido definidas pelo próprio programador ou por outros programadores. No caso do liquidificador, a rotina foi definida pelo próprio fabricante. O usuário só precisa saber o que precisa saber como operá-lo: que tipos de coisas deve fornecer como **entrada** (*input*) e o que deve esperar receber como **saída** (*output*). # # # Funções facilitam sua vida por dois motivos principais. # # 1. Permitem ao programador **abstrair** computações, sem precisar se preocupar a todo momento sobre os mínimos detalhes de como elas são de fato realizadas. Imagine se você tivesse que se preocupar com os detalhes sobre como um texto é imprimido na tela de seu computador toda vez que você precisasse desta funcionalidade... Felizmente a função `print`, que utilizamos no nosso programa "Hello World", permite que esta rotina seja abstraída para você, o que facilita bastante seu aprendizado! # # 2. Permitem **compartilhar** e **reutilizar** código. Se você construir uma função que possa ajudar outras pessoas também, por que não compartilhar? Isso acontece bastante na comunidade de programadores, e os ajuda a não ficar "reinventando a roda" quando precisam de alguma funcionalidade que já foi implementada por alguém. # Para começar a entender como trabalhar com funções, precisamos conhecer seus três componentes principais: *(i)* **nome**, *(ii)* **parâmetros** e *(iii)* **corpo**. # # Dar um **nome** às funções é uma forma simples de mantermos uma referência a elas. Embora possamos nomear funções conforme nossa vontade, é recomendável escolhermos nomes que nos digam algo sobre seu funcionamento. É fácil lembrar que a função `print`, por exemplo, serve para imprimir algo na tela. # # Os **parâmetros** fornecem um meio para "afinarmos" o comportamento de uma função para nossas necessidades. Por exemplo, no caso do liquidificador, alguns parâmetros relevantes seriam o tempo de processamento, ou a velocidade de rotação do motor. Os dados de entrada (*inputs*) são também passados para as funções através de parâmetros. No jargão da programação, nos referimos aos valores que passamos no lugar de cada um dos parâmetros como **argumentos**. Pense em um parâmetro como um *placeholder* para um valor, enquanto o argumento é o valor em si, passado para dentro da função através de um parâmetro. # # Por fim, no **corpo** da função especificamos todas as etapas que devem ser realizadas por ela. Estas etapas incluem o processamento dos dados de entrada (*inputs*) e a construção do resultado que ela produzirá como saída (*output*). No fim das contas, a ideia é que as computações descritas no corpo da função sejam abstraídas para o usuário da função. # ### Objetivos. # # Após esta aula você deverá ser capaz de: # * **Definir** e **executar** funções; # * Delimitar o **escopo** de uma função; # * Reutilizar funcionalidades distribuídas em **pacotes** pela comunidade *Python*. # ## 1. Definindo uma nova função # Para definir uma nova função precisamos obedecer à seguinte sintaxe: # * a palavra `def` indica que uma nova função está sendo definida; # * após `def`, deve ser escrito o **nome** da função; # * após o nome da função, entre parênteses, os **parâmetros** são separados por vírgulas; # * Os dois pontos `:` após os parênteses indica que o **corpo** da função vem a seguir, no bloco de código abaixo; # * O bloco de código deve ser escrito com uma **indentação**, o distanciamento em relação à margem esquerda da célula; # * No fim do corpo da função, o valor de saída (*output*) é indicado após a palavra `return`. # Para ilustrar, vamos construir uma nova função, batizada com o nome `foo` (poderia ser qualquer outro nome, tipo `dinossauro`). Ela simplesmente deve imprimir no console os valores que passamos em cada um dos parâmetros e, no fim, retornar como *output* o valor $1$. # In[1]: def foo(par1, par2): print(par1) print(par2) return 1 # Uma vez definida, a função fica guardada na memória, mas não é automaticamente executada. Para de fato executá-la, precisamos **chamá-la**, escrevendo seu nome e passando argumentos no lugar dos parâmetros, entre parênteses. # In[2]: foo("Santos", "Dumont") # Vamos definir agora uma função chamada `funcaoSoma`, que simplesmente soma dois números e retorna o resultado. Ela possui os parâmetros `n1` e `n2`, que esperam receber como valores (ou argumentos) dois valores numéricos. # In[3]: def funcaoSoma(n1,n2): res = n1 + n2 return res # In[4]: funcaoSoma(3,5) # In[5]: funcaoSoma(-3,23) # In[6]: funcaoSoma(42,0) # Podemos também armazenar o resultado de funções em variáveis! Vamos declarar as variáveis `a`, `b`, `c` e `d`, com quaisquer valores numéricos. Em seguida, usaremos a função `funcaoSoma` para somar `a` e `b` e armazene o resultado em uma variável `a_b`. Depois, usaremos novamente a função `funcaoSoma` para somar `c` e `d` e armazene o resultado em uma variável `c_d`. Finalmente, somaremos os valores em `a_b` e `c_d` usando a mesma `funcaoSoma`, armazenando o resultado final em uma variável `resFinal`. # In[7]: a = 3 b = 5 c = 9 d = 11 a_b = funcaoSoma(a,b) c_d = funcaoSoma(c,d) resFinal = funcaoSoma( a_b, c_d ) resFinal # Podemos também passar resultados de funções como argumentos para outras funções, sem precisar utilizar variáveis intermediárias! Podemos, por exemplo, realizar as mesmas computações da célula acima sem precisar declarar `a_b` e `c_d`. # In[8]: a = 3 b = 5 c = 9 d = 11 resFinal = funcaoSoma( funcaoSoma(a,b), funcaoSoma(c,d) ) resFinal # ## 2. Escopo de funções # Funções operam em um "contexto" próprio. # É como se criássemos uma "bolha" toda vez que executamos uma função. Tudo o que está dentro desta bolha não é visível de fora dela, mas todo o conteúdo que está fora dela é visível de dentro. # Nos referimos ao contexto de "dentro da bolha" como o **escopo local** da função, e tudo o que está fora como o **escopo global**. # # Sendo assim, uma funçao "enxerga" todas as variáveis em escopo local, ou seja, aquelas que são definidas dentro dela própria (incluindo os parâmetros); e as variáveis em escopo global, definidas fora dela. # No entanto, variáveis locais, definidas dentro de funções, não são visíveis de fora delas! # In[9]: a = 3 print("Variável a ANTES da execução da função:",a) b = 5 print("Variável b ANTES da execução da função:",b) def foo(): # A função é apenas definida aqui a = 7 print("Variável a DENTRO da função foo:",a) print("Variável b DENTRO da função foo:",b) m = 11 print("Variável m DENTRO da função foo:",m) print('---') foo() # A função apenas é de fato executada aqui print('---') print("Variável a APÓS a execução da função",a) print("Variável b APÓS da execução da função:",b) print("Variável m",m) # **Obs:** Um mesmo nome pode ser dado para variáveis em escopos diferentes (que podem conter valores diferentes)! Nestes casos, a variável que foi definida em escopo local prevalece sobre a global. # ## 3. Reutilizando funcionalidades # O segredo para um bom desempenho como programador é jamais reinventar a roda! Sempre que possível, devemos buscar reutilizar código que já foi escrito por outros programadores. # **Pacotes** são uma forma eficiente usada pelos programadores para estruturar e compartilhar seu código com a comunidade, sendo criados com o intuito de entregar aos usuários um conjunto de funcionalidades. # O código dentro de um pacote é organizado em **módulos**, cada qual contendo um conjunto de funções e variáveis que implementam funcionalidades mais específicas. # # # Veja uma lista de pacotes potencialmente interessantes para um biólogo: # * [Biopython](http://biopython.org/) com funcionalidades específicas para aplicações em biologia molecular computacional, na **bioinformática**; # * [Numpy](http://www.numpy.org/) para computação científica, com representações e operações otimizadas sobre objetos matemáticos; # * [Pandas](http://pandas.pydata.org/) para representação de *Data frames* e análise de dados; # * [Matplotlib](https://matplotlib.org/) para construção de figuras (visualização de dados); # * [Statsmodels](http://www.statsmodels.org/stable/index.html) para trabalhar com modelos estatísticos; # * [ScikitLearn](http://scikit-learn.org/stable/) para trabalhar com algoritmos de aprendizado de máquina; # * [Networkx](https://networkx.github.io/) para construir modelos em redes complexas; # * [ArcPy](http://desktop.arcgis.com/en/arcmap/10.3/analyze/arcpy/what-is-arcpy-.htm) é um pacote para automação de tarefas de análise e geração de mapas em ArcGis; # * [Matplotlib Basemap](https://matplotlib.org/basemap/) é um conjunto de ferramentas para plotar mapas em python, utilizando como base a *Matplotlib*; # * [Pygbif](http://pygbif.readthedocs.io/en/latest/) é um cliente *Python* que facilita o acesso a dados de ocorrência de espécies no [GBIF](https://www.gbif.org/) (Global Biodiversity Information Facility). # Em *Python*, existem algumas funções que são carregadas com a inicialização do interpretador, por serem utilizadas de forma mais ampla pelos programadores. Alguns exemplos são as funções `print`, `type`, `range` e `open`. # Outras, no entanto, são mais específicas e portanto não são carregadas automaticamente. # Usamos a palavra `import` para carregar um pacote ou módulo. # O módulo `math`, por exemplo, reúne um conjunto de funções e constantes que facilitam muito trabalhar com elementos da matemática. Estas funções não são carregadas automaticamente, e portanto devemos **importar o módulo** para utilizá-las. # Vamos carregar o módulo `math` e, em seguida, executar a função `factorial` provida por ele. Conforme podemos consultar na documentação, esta função espera um número como *input* e retorna seu fatorial, como *output*. # In[10]: import math print("Fatorial de 1:",math.factorial(1)) print("Fatorial de 2:",math.factorial(2)) print("Fatorial de 3:",math.factorial(3)) print("Fatorial de 10:",math.factorial(10)) # --- # ## Exercícios # **Ex 1.** Defina uma função `calculaMedia`, que calcula a média entre **três** números quaisquer. Em seguida, execute sua função com valores diferentes. O que acontece se você definir a função com três parâmetros mas passar um número diferente de argumentos? # In[11]: def calculaMedia(n1,n2,n3): res = (n1+n2+n3)/3 return res # In[12]: calculaMedia(1,3,5) # In[13]: calculaMedia(1,2,5) # In[14]: # Este código vai gerar um erro: A função esperava três argumentos mas nós só passamos 2 calculaMedia(1,5) # **Ex 2.** Defina uma função `multiplica`, que multiplica dois números quaisquer. Em seguida, declare as variáveis `a`, `b` e `c` com quaisquer valores numéricos. Multiplique `a` e `b` utilizando a função `multiplica` e, em seguida, execute novamente a função `multiplica` para multiplicar o resultado do passo anterior pelo número em `c`. # In[15]: def multiplica(n1,n2): return n1*n2 # In[16]: a=3 b=5 c=7 multiplica( multiplica(a,b), c) # **Ex 3.** A função `exponencia` abaixo deve realizar uma exponenciação do número `n1` à potência `n2`. Mas ao executar a célula para definir a função recebemos uma mensagem de erro. Corrija o código e experimente executar a função com alguns pares de números. # In[17]: def exponencia(n1,n2): return n1**n2 # In[18]: exponencia(2,3) # In[19]: exponencia(5,2) # **Ex 4.** A função `mensagemMeuNome` abaixo deveria receber como argumento o seu nome, imprimir uma mensagem com ele e, por fim, retornar o seu nome como *output*. No entanto, existe um *bug*. Você consegue identificá-lo e corrigí-lo? # In[20]: nome = "Luke Skywalker" def mensagemMeuNome( meuNome ): print("Olá! Me chamo", meuNome) return meuNome mensagemMeuNome("Pedro") # insira seu nome entre parênteses # **Ex 5.** A função `type` é automaticamente carregada com a inicialização do interpretador *Python*. Você consegue descobrir o que ela faz? Experimente passar diferentes tipos de dados. # In[21]: # A função type retorna o tipo do valor passado como argumento print("Tipo de 'Algum texto':", type("Algum texto") ) print( "Tipo de 43:",type(43) ) print( "Tipo de True:", type(True) ) print( "Tipo de 32.21:", type(32.21) )