Funções
Em linguagens de programação, funções se comportam como black boxes. Nós não precisamos de saber como elas funcionam, basta saber o que elas esperam de entrada, e o que elas nos dão como saída/ resultado. Pense um carro; não precisamos saber como ele funciona para dirigí-lo. O mesmo vale para um computador ou quase qualquer outra ferramenta mais complexa que um martelo. Nós abstraímos o objeto, o que importa é sua função (o que ele faz) e como usá-lo, e não como ele funciona ou é criado.

Além de tornar objetos mais fáceis de se usar (imagine precisar saber como um computador funciona para usá-lo!), abstrair também facilita a evolução tecnológica. Dado que você já sabe dirigir, a fabricante pode mudar o modelo de um mesmo carro para usar um motor elétrico ao invés de um de combustão interna, e você não precisa de aprender nada para adquirir e usar o modelo novo.
Em linguagens de programação, funções também são formas de separar problemas. Tomemos novamente o exemplo de um carro: ele é formado por diversos componentes (peças, sistemas, etc.) Em uma fabricante de veículos há responsáveis pelo carro em geral, mas a maior parte do trabalho é feito por times especializados nas suas partes/componentes. Um engenheiro trabalhando em um motor mais eficiente não precisa de conversar com um que esteja desenhando uma carroceria mais aerodinâmica, desde que o trabalho de cada um seja bem limitado/especificado – afinal, o motor precisa caber na carroceria.
Separar um problema/produto/programa em componentes menores não só tem o potencial de melhorar a eficiência de sua resolução/produção/escrita, mas às vezes é o que a viabiliza.
1. Definindo funções
No nosso aprendizado Python até aqui, já usamos diversas funções:
print
, range
, input
. Imagine se todas as vezes que quiséssemos
mostrar alguma coisa na tela, nosso código precisasse de reescrever
(reimplementar) a função print
. Felizmente, basta sabermos que
print
existe e como usá-la; não precisamos repetir código (e
evitamos todos os problemas que isso acarreta).
Ao contrário do caso de print
, todo o código que escrevemos até aqui
precisaria ser repetido se quiséssemos utilizá-lo novamente. Usando
nosso código anterior, se quisermos calcular dois números da sequência
de Fibonacci e multiplicá-los, precisamos de repetir o código duas
vezes, colocando os números em variáveis diferentes, e então
multiplicá-las. Para facilitar o processo e não repetir código,
bastaria definir uma função nthfibo
(de n-ésimo Fibonacci) e
chamá-la duas vezes:
def nthfibo(n): # calcula o n-ésimo número da sequência de Fibonacci pass # pass é uma keyword que não faz nada; é só um placeholder # para o código que ainda vamos escrever nthfibo(4) * nthfibo(5)
O exemplo acima já nos dá uma ideia da sintaxe usada para definir
funções. A keyword def
inicia a definição; em seguida vem o nome da
função (a nomenclatura deve seguir as mesmas regras de nomes de
variáveis), e em seguida os nomes de seus valores de entrada
(opcionais, também chamados de argumentos ou parâmetros) entre
parênteses e separados por vírgulas, e então dois pontos (:
). Na
linha seguinte vem um bloco de código indentado, que é o corpo da
função. Esse corpo é executado quando chamamos a função.
No exemplo nthfibo
, o nome da função é (obviamente) nthfibo
, a
função possui somente um argumento n
que devemos fornecer ao chamar
a função. Para chamar uma função escrevemos seu nome seguido de
parênteses, com seus argumentos entre os parênteses e separados por
vírgula. Note que no exemplo as chamadas de nthfibo(4)
e
nthfibo(5)
estão fora do corpo da função.
1.1. Exercício: função nthfibo
Escreva função nthfibo
(pode usar o código do exercício anterior), e
calcule o décimo número da sequência fazendo a soma do resultado de
duas chamadas de nthfibo
; Lembre-se: \[F_n = F_{n-1} + F_{n-2}
\qquad (n > 2)\]
2. ‘Retornando’ resultados
Você provavelmente teve problemas respondendo o exercício anterior. Em
Python (e em quase todas as outras linguagens de programação), é
preciso especificar o resultado da função usando a keyword
return
. Compare o resultado de chamar as duas funções abaixo:
def nothing(): pass def two(): return 2 print(nothing()) print(two())
Quando uma função não possui a keyword return
especificando seu
resultado/saída, ela não retorna nada. Em Python, nada é
representado por None
. Poderíamos escrever a função nothing
mais
explicitamente:
def nothing(): # equivalente à definição anterior return None
None
não parece muito útil, mas na verdade tem seus usos. Por
exemplo, você provalmente já tentou logar em algum site em que você
não se lembrava do seu nome de usuário ou o email que usou; ao entrar
os seus dados e receber a mensagem de que aquele usuário/email não
estava castrado no sistema, algum código no servidor do site
provavelmente retornou None
(ou null
, como costuma se chamar em
outras linguagens).
Importante: uma função só retorna uma vez. Após retornar, ela não
faz mais nada. Você pode até escrever código depois de um return
,
mas ele não será executado (tente!).
2.1. Exercício: função nthfibo
(second time is the charm!)
Defina a função nthfibo
usando return
. Note como você usa mais de
um return
, correspondendo aos ‘casos possíveis’ da função.
3. Escopo
Até aqui nós temos escrito programas pequenos, mas programas reais podem ser muito longos (SQLite — uma base de dados que provavelmente está presente no seu computador e no seu celular — tem por volta de 238 mil linhas de código; o programa que implementa a linguagem Python está se aproximando de um milhão de linhas de código). Em programas desta escala, até mesmo não repetir nomes de variáveis passa a ser um problema. Felizmente, esse é um não-problema, dado que a maioria das variáveis tem um escopo definido.
Por exemplo, ao definir nthfibo
, escolhemos n
como o nome de seu
único argumento. Ao invocarmos nthfibo(10)
, a variável n
assume o
valor 10
dentro do corpo da função nthfibo
, mas seu valor fora
dela (se houver) continua o mesmo. Veja:
n = 42 print(n) nthfibo(10) print(n)
A lógica é a seguinte: apesar de já existir uma variável n
‘fora’ de
nthfibo
, a definição de nthfibo
redeclara uma variável n
com
escopo local; para todos efeitos, esta é uma nova variável, que nada
interfere com a outra variável declarada no escopo de fora de
nthfibo
.
Apesar de não ser possível (normalmente) modificar variáveis de um
escopo exterior de uma função, a função pode acessar estas variáveis
(desde que seus nomes não conflitem com nomes internos, como é o caso
do exemplo anterior entre n
e nthfibo
). Veja:
pi = 3.14159 def sphere_area(r): return 4 * pi * r**2 def sphere_volume(r): return 4/3 * pi * r**3
Se precisarmos de modificar uma variável de um escopo externo com um valor calculado por uma função, basta retornar o valor e fazer o assignment.
def increment(n): return n + 1 n = 5 increment(n) print(n) n = increment(n) print(n)
4. Funções como argumentos de funções
Note que nada impede que funções sejam argumentos de outras
funções. Podemos definir uma função map_sum
:
def map_sum(f, n): total = 0 for i in range(1, n + 1): total = total + f(i) return total def add_one(n): return n + 1 map_sum(add_one, 10)
4.1. Exercício: funções de alta ordem
É importante treinar a leitura de código tanto quanto sua escrita. O
que faz a função map_sum
? Qual é o resultado de map_sum(nthfibo,
10)
e o que ele representa?
5. Recursão
5.1. Exercício: Fibonacci, recursivamente
Até aqui, calculamos a sequência de Fibonacci usando loops. Mas a definição matemática da sequência sugere uma
implementação mais simples e direta, mas que só é possível com
chamadas de funções. Tente escrever essa versão, chamando-a de
nthfibo_rec
. Para todo inteiro \(n\) maior que zero, deve valer que
nthfibo(n) == nthfibo_rec(n)
.

Note que enquanto blackboxes, tanto a versão recursiva quando a versão que usa loops (chamada iterativa) são equivalentes, pois tem as mesmas entradas e saídas. Mas do ponto de vista computacional, existe uma diferença entre elas: compare a execução das duas versões para \(n = 35\). E para \(n = 10000\).
6. Retornos múltiplos
Uma função pode retornar mais de um valor, basta separar os valores a serem retornados por vírgulas:
def nthfibo_rec(n): if n == 1: return 0, 0 elif n == 2: return 1, 0 elif n > 2: prev, prevprev = nthfibo_rec(n - 1) return prev + prevprev, prev
Teste esta versão de nthfibo_rec(n)
com \(n = 35\) e \(n =
10000\). (Ignore o fato de que ela retorna tanto o n-ésimo e o
n-1-ésimo números da sequência.)
7. Argumentos opcionais
Como vimos com range
, uma função Python pode ter argumentos
opcionais. Isto é útil quando (assim como no caso de range
!) há um
valor ‘padrão’ para um argumento, que mesmo assim por vezes queremos
alterar. Por exemplo, ao definir uma função que calcula o logaritmo de
um número, podemos ter dois argumentos: o primeiro (obrigatório) seria
o número do qual queremos o logaritmo, e o segundo (opcional) seria a
base do logaritmo (cujo valor padrão pode ser a base natural \(e\) se
quisermos agradar matemáticos, ou \(2\) se quisermos agradar
programadores).
def logaritmo(n, b=2): pass logaritmo(8) # 3 logaritmo(8, 8) # 1
7.1. Exercício: logaritmo
Podemos usar uma versão do método de Newton para implementar uma função que calcule o logaritmo de um número maior que 1 em uma certa base. Para podermos usar o método, precisamos de:
- ter um limite inferior e um superior para valor que queremos calcular;
- dada uma possível solução (um chute), ter uma forma de verificar o quão bom é esse chute;
Por exemplo, para o caso da raiz quadrada de um número maior que 1, temos:
- a raiz de um número está entre 1 (limite inferior para a solução) e o número (limite superior para a solução);
- para ver o quão bom é um chute \(c\), basta calcular a distância de entre \(c^2\) e o número em questão (\(|n - c^2|\));
Preenchidos esses dois requisitos, e dado um chute/solução potencial, basta verificar se o chute é bom ou não. Se ele for muito próximo do resultado real, paramos e propomos o chute como solução final. Caso contrário, iremos refinar o chute até que ele fique próximo o suficiente do valor que queremos.
Uma forma de fazer isso é a seguinte: para obter um chute, usamos a média do limite inferior e superior. Se o chute for menor do que o valor que queremos, definimos o chute como novo limite inferior, e tentemos de novo; se o chute for maior, definimos o chute como novo limite superior, e tentemos de novo. Paramos quando o chute for arbitrariamente próximo do valor que buscamos (por exemplo, a diferença entre a raiz verdadeira e o nosso chute é menor do que 0.001).
Por exemplo, para o caso da raiz quadrada de um número \(n\) maior que 1, nossos limites iniciais são \(1\) e \(n\). O nosso primeiro chute \(c\) será \(\frac{n + 1}{2}\). Verificamos se o chute é bom: se \(c^2\) for muito próximo de \(n\) (por exemplo, se \(|n - c^2| < 0.05\)), retornamos \(c\) como solução. Caso contrário, tomamos \(c\) como novo limite (inferior ou superior, a depender de \(c^2\) ser maior ou menor que \(n\)), e calculamos um novo chute \(c'\) usando o mesmo método (média aritmética dos limites inferior e superior). Fazemos com \(c'\) a mesma coisa que com \(c\), até que cheguemos num chute bom o suficiente.
Neste exercício, implemente este método (ou um melhor) para calcular o
logaritmo de um número maior do que 1. A função logarithm
deve ter
três argumentos, n
para o número do qual queremos calcular o
algoritmo, b
para a base do logaritmo (opcional, valor padrão 2
),
e tol
(opcional, valor pardrão 0.01
). tol
é a tolerância, isto
é, o quão próximo o chute deve ser para julgarmos que ele serve de
resposta. Use a função abs
(que retorna o valor absoluto de um
número) para facilitar o cálculo da proximidade do chute.
Antes de começar a programar, pense sobre o quê seriam os passos 1 e 2 para logaritmo (e compare com os passos 1 e 2 para a raiz quadrada). Se achar mais fácil, implemente a raiz quadrada antes de implementar o logaritmo, pois há muitas partes comuns entre os dois exercícios. Note que há várias respostas possíveis, qualquer uma delas é válida, mas algumas respostas são mais rápidas de calcular ou mais fáceis de programar.
7.2. Exercício: taxa de performance funcional
Calcule a taxa de performance a ser paga caso um fundo de investimento bata seu benchmark e o valor final líquido do investimento. (Você pode usar a resposta do exercício anterior como base.) A função deve receber 4 argumentos: o valor inicial do investimento, o valor final bruto do investimento, a valorização percentual do benchmark (4.5 é 4.5% de valorização), e (como argumento opcional) o percentual da taxa de performance (o padrão deve ser 20, significando uma taxa de 20%). Se o fundo não superou o benchmark (ou teve perdas), a taxa de performance a ser paga deve ser zero, e o valor final líquido deve ser igual ao valor final bruto.