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")
Santos
Dumont
Out[2]:
1

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)
Out[4]:
8
In [5]:
funcaoSoma(-3,23)
Out[5]:
20
In [6]:
funcaoSoma(42,0)
Out[6]:
42

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
Out[7]:
28

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
Out[8]:
28

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)
Variável a ANTES da execução da função: 3
Variável b ANTES da execução da função: 5
---
Variável a DENTRO da função foo: 7
Variável b DENTRO da função foo: 5
Variável m DENTRO da função foo: 11
---
Variável a APÓS a execução da função 3
Variável b APÓS da execução da função: 5
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-9-961615b014f8> in <module>()
     18 print("Variável a APÓS a execução da função",a)
     19 print("Variável b APÓS da execução da função:",b)
---> 20 print("Variável m",m)

NameError: name 'm' is not defined

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 com funcionalidades específicas para aplicações em biologia molecular computacional, na bioinformática;
  • Numpy para computação científica, com representações e operações otimizadas sobre objetos matemáticos;
  • Pandas para representação de Data frames e análise de dados;
  • Matplotlib para construção de figuras (visualização de dados);
  • Statsmodels para trabalhar com modelos estatísticos;
  • ScikitLearn para trabalhar com algoritmos de aprendizado de máquina;
  • Networkx para construir modelos em redes complexas;
  • ArcPy é um pacote para automação de tarefas de análise e geração de mapas em ArcGis;
  • Matplotlib Basemap é um conjunto de ferramentas para plotar mapas em python, utilizando como base a Matplotlib;
  • Pygbif é um cliente Python que facilita o acesso a dados de ocorrência de espécies no GBIF (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))
Fatorial de 1: 1
Fatorial de 2: 2
Fatorial de 3: 6
Fatorial de 10: 3628800

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)
Out[12]:
3.0
In [13]:
calculaMedia(1,2,5)
Out[13]:
2.6666666666666665
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)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-14-e266eef7ddd9> in <module>()
      1 # Este código vai gerar um erro: A função esperava três argumentos mas nós só passamos 2
----> 2 calculaMedia(1,5)

TypeError: calculaMedia() missing 1 required positional argument: 'n3'

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)
Out[16]:
105

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)
Out[18]:
8
In [19]:
exponencia(5,2)
Out[19]:
25

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
Olá! Me chamo Pedro
Out[20]:
'Pedro'

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) )
Tipo de 'Algum texto': <class 'str'>
Tipo de 43: <class 'int'>
Tipo de True: <class 'bool'>
Tipo de 32.21: <class 'float'>