import numpy as np
import time6 Funções
6.1 Introdução
Até aqui, os programas escritos consistem em sequências de instruções, condicionais e laços que manipulam objetos como números, listas e dicionários. Essa forma é suficiente para resolver problemas simples, mas rapidamente se torna difícil de manter quando o código cresce ou quando a mesma lógica precisa ser reutilizada várias vezes.
Funções são o principal mecanismo de abstração em programação. Elas permitem encapsular um conjunto de instruções sob um nome, definir claramente quais informações entram e quais resultados saem, e separar a lógica interna da interface externa. Em termos computacionais, funções organizam o fluxo de execução, delimitam escopos de variáveis e tornam explícita a transformação entre inputs e outputs – conceito central também em Economia, onde frequentemente modelamos relações funcionais entre variáveis.
Neste capítulo, o objetivo é compreender o papel das funções como ferramenta de organização, reutilização e clareza lógica. Ao final, o aluno deverá ser capaz de estruturar programas de forma modular e compreender como funções são usadas extensivamente nas bibliotecas científicas que serão estudadas nas próximas aulas.
6.1.1 Pré-requisitos
Para trabalhar com funções e todas as suas funcionalidades, utilizaremos dois pacotes externos:
numpytime
6.2 Importância da modularização
Até aqui, os programas foram escritos como sequências lineares de instruções executadas de cima para baixo. Esse modelo é suficiente quando resolvemos um único problema específico com um único conjunto de dados. No entanto, ele se torna inadequado quando precisamos aplicar a mesma regra várias vezes ou quando o código começa a crescer.
Considere o seguinte exemplo. Suponha que desejamos calcular a média de diferentes conjuntos de notas:
notas1 = [7.5, 8.0, 6.5]
media1 = sum(notas1) / len(notas1)
print(media1)
notas2 = [9.0, 8.5, 7.0]
media2 = sum(notas2) / len(notas2)
print(media2)7.333333333333333
8.166666666666666
O código funciona, mas a regra de cálculo aparece repetida. Se decidirmos alterar o critério – por exemplo, aplicar um ajuste ou excluir a menor nota – será necessário modificar todas as ocorrências manualmente. Esse padrão aumenta a probabilidade de erro e dificulta a manutenção. O problema não está no cálculo em si, mas na forma como ele está organizado. Estamos misturando duas coisas distintas: os dados, que variam, e a regra de transformação, que permanece a mesma. Uma solução mais robusta é separar essas duas dimensões.
É aqui que entram as funções, que nos permitem encapsular determinado conjunto de instruções e nomeá-los. Assim, basta chamar a função pelo seu nome para aplicar em certo conjunto de dados o mesmo conjunto de instruções. O uso de funções introduz o conceito de modularização. Em vez de escrever um bloco contínuo de instruções, passamos a estruturar o programa como um conjunto de módulos (funções), cada um responsável por uma transformação específica entre entrada e saída. Do ponto de vista computacional, isso significa que isolamos partes do código, reduzimos redundância e tornamos explícita a relação entre argumentos recebidos e valores retornados.
A partir desse ponto, o objetivo é compreender formalmente como funções são definidas, como recebem argumentos e como retornam resultados. O foco deixa de ser apenas executar comandos e passa a ser estruturar programas de forma lógica e reutilizável.
6.3 O que é uma função?
Formalmente, no contexto das linguagens de programação, uma função é uma sequência nomeada de instruções, que executa algum tipo de operação específica mas não necessariamente numérica, como é o caso das funções matemáticas. A ideia essencial por trás de uma função é a de juntar algumas tarefas comuns ou repetidas e criar uma função para que, em vez de escrever o mesmo código várias vezes, possamos chama-la pelo nome e reutilizar o conjunto de instruções nela contida sempre que necessário.
Caso o objetivo de dividir um programa em funções ainda não tenha ficado claro, saiba que:
Criar uma nova função dá a oportunidade de nomear um grupo de instruções, o que deixa o seu programa mais fácil de ler e de depurar.
As funções podem tornar um programa menor, eliminando o código repetitivo. Depois, caso precise fazer alguma alteração, basta fazê-la em um lugar só.
Dividir um programa longo em funções permite depurar as partes uma de cada vez e então reuni-las em um conjunto funcional.
As funções bem projetadas muitas vezes são úteis para muitos programas. Uma vez escritas e depuradas, você pode reutilizar as funções em programas fora daquele para o qual elas foram originalmente construídas.
Existem várias funções nativas no Python (e.g., type() para encontrar o tipo de um objeto) e mesmo dentro de bibliotecas com as quais já trabalhamos um pouco (e.g., numpy.random.uniform() para sortear números aleatórios de acordo com uma distribuição uniforme). À partir de agora veremos como podemos criar nossas próprias funções dentro da linguagem. No Python, a sintaxe para criar uma função personalizada é dada por:
def nome_da_funcao(argumentos):
<instruções> Assim como nos loops, para definir uma nova função no Python é preciso começar com uma palavra-chave, nesse caso def. O nome que daremos à função vem logo em seguida. É através desse nome, que chamaremos essa função em outras partes do nosso código. Entre parênteses definimos os argumentos que a função recebe para realizar o conjunto de instruções em <instruções>. Note, mais uma vez, que todo o bloco de código que estiver identado e abaixo da linha de cabeçalho da função fará parte da função.
Vejamos um exemplo simples. A função frase_cinema apresentada a seguir tem como objetivo imprimir uma das frases mais emblemáticas da história do cinema, originalmente introduzida na série de filmes Star Wars, criada por George Lucas.
def frase_cinema():
print('Que a força esteja com você!')Note que nesse caso, o parênteses logo após o nome da função está vazio. Isso quer dizer que essa função não recebe argumentos, apenas printa a frase entre aspas sempre que for chamada. Para chamá-la, basta usar o nome frase_cinema seguido dos parênteses vazios:
frase_cinema()Que a força esteja com você!
6.4 Argumentos de uma função
Na função anterior, não tínhamos nenhum argumento. Ou seja, toda vez que chamarmos a função frase_cinema, ela fará a mesma coisa. Pode até ser que seja esse o objetivo, apenas economizar linhas de código. A ideia de função, no entanto, é muito mais poderosa do que simplesmente uma “abreviação” de um monte de linhas de código. A abstração implícita no conceito de função é poderosa o suficiente para garantir que a função retorne coisas diferentes caso você altere algum argumento. Vamos falar disso a seguir.
6.4.1 Keyword arguments e default values
Comecemos criando uma nova função my_func que receba como argumentos um nome próprio e uma localização.
def my_func(name,place):
strf='Olá '+name+'! Você é de '+place+'?'
print(strf)
my_func('Emily','Paris')Olá Emily! Você é de Paris?
Ao chamar a função my_func, ela combina os argumentos Emily e Paris dentro de um único string e printa esse string final. O que acontece, no entanto, se especificarmos o argumento place primeiro e depois o name?
my_func('Ribeirão Preto','Roberto')Olá Ribeirão Preto! Você é de Roberto?
Um pouco estranho, não? A razão é que nesse exemplo utilizamos os chamados argumentos posicionais. Ou seja, a função vai assumir que o primeiro argumento é o name e o segundo é o place, não importa o que tenhamos passado como argumento. Para lidar com isso, a podemos atribuir um nome, ou _palavra-chave, a cada um dos argumentos. Nesse caso, será a palavra-chave, e não a posição, que vai determinar qual o valor atribuído a cada argumento dentro da função.
my_func(place='Ribeirão Preto',name='Roberto')Olá Roberto! Você é de Ribeirão Preto?
Note que agora a posição passou a ser irrelevante porque colocamos os nomes de cada um dos argumentos. E se quiséssemos dar ainda mais flexibilidade à função, só especificando um subconjunto dos argumentos? Para que isso funcione, nós precisamos especificar os valores-padrão dos argumentos caso eles não sejam fornecidos. Abaixo uma função que faz exatamente isso.
def total_calc(bill_amount,tip_perc=10):
total=bill_amount*(1 + tip_perc/100)
total=round(total,2)
print('O valor final da sua conta foi: R$'.format(total))Nesse caso, o argumento bill_amount é obrigatório. Por outro lado, o argumento tip_perc vai assumir o valor 10 por padrão, caso não seja fornecido nenhum outro valor em seu lugar quando chamarmos a função.
6.4.2 Argumentos arbitrários
Após discutir o uso de keyword arguments na definição de funções em Python é natural avançar para uma questão relacionada: o que fazer quando sequer sabemos, de antemão, quantos argumentos serão fornecidos? Em outras palavras, é possível definir funções capazes de operar com um número variável de entradas? A resposta é sim! O Python permite construir funções que acomodam uma quantidade indefinida de argumentos, preservando generalidade e reutilização de código. Para ilustrar essa ideia, podemos definir uma função simples chamada my_var_sum, cuja finalidade é retornar a soma de todos os valores numéricos recebidos na chamada, independentemente de quantos sejam.
def my_var_sum(*args):
sum = 0
for arg in args:
sum=sum+arg
print('A soma total dos números fornecidos é igual a: '+str(sum))Observe como a definição da função agora tem *args ao invés de argumentos nomeados. No corpo da função, fazemos um loop em args até usarmos todos os argumentos. A função my_var_sum retorna a soma de todos os números passados como argumentos. Olha só o que acontece quando chamamos a função com diferentes números de argumentos:
my_var_sum(99,10,54,23)
my_var_sum(9,87)
my_var_sum(5,21,36,79,45,65)
my_var_sum(1)A soma total dos números fornecidos é igual a: 186
A soma total dos números fornecidos é igual a: 96
A soma total dos números fornecidos é igual a: 251
A soma total dos números fornecidos é igual a: 1
Mas e se eu quiser passar não apenas uma sequência de valores, mas uma sequência de valores com nomes? Não se preocupe, existe uma possibilidade de fazer isso, usando o **kwargs na definição dos argumentos.
Qual é a diferença entre *args e **kwargs? A diferença é que você vai passar uma sequência de tamanho arbitrário de parâmetros, cada um deles nomeado. Nesse caso, o Python entende os elementos em **kwargs como parte de um dicionário. Cada elemento passado é um par chave-valor, e dentro da função você tem que desempacotar o valor da chave. Vamos seguir alguns exemplos.
def myFun(**kwargs):
for key, value in kwargs.items():
print(key+' == '+value)
myFun(first='Geeks', mid='for', last='Geeks')first == Geeks
mid == for
last == Geeks
Podemos misturar os tipos de argumentos (posicionais versus *args/*kwargs)? Sim!
def myFun(arg1, **kwargs):
for key, value in kwargs.items():
print(arg1+key+' == '+value)
myFun("Hi - ", first='Geeks', mid='for', last='Geeks')Hi - first == Geeks
Hi - mid == for
Hi - last == Geeks
6.4.3 Execução condicional dentro de uma função
As funções podem retornar objetos booleanos também, o que pode ser conveniente para esconder testes complicados dentro de uma função. Por exemplo:
def is_divisible(x, y):
if x % y == 0:
print(True)
else:
print(False)
is_divisible(6, 4)False
6.4.4 Iteração dentro de uma função
Da mesma forma que podemos ter condições lógicas dentro da função, podemos ter blocos de repetição (for e while) dentro da função, assim como no caso de argumentos arbitrários. Vamos olhar para um outro exemplo em que a função recebe uma lista de strings como argumento e faz operações em cada um dos elementos, um a um:
states = [' Alabama ','Georgia!','Georgia','georgia','FlOrIda','south carolina##','West virginia?']
def clean_strings(lista_strings):
result=[]
for value in lista_strings:
value = value.strip()
value = value.title()
value = value.replace('#','')
value = value.replace('?','')
value = value.replace('!','')
result.append(value)
print(result)
print(states)
clean_strings(states)[' Alabama ', 'Georgia!', 'Georgia', 'georgia', 'FlOrIda', 'south carolina##', 'West virginia?']
['Alabama', 'Georgia', 'Georgia', 'Georgia', 'Florida', 'South Carolina', 'West Virginia']
Conforme as funções se tornam mais complexas, incorporando lógica condicional e laços, torna-se fundamental entender como extrair resultados de funções. Até aqui, muitos exemplos mostraram funções que apenas imprimem valores; na seção seguinte analisaremos o papel do comando return e sua importância na prática.
6.5 Valor de retorno de uma função
A instrução return é o mecanismo pelo qual uma função envia um resultado de volta ao ponto em que foi chamada. Do ponto de vista lógico, uma função representa uma transformação entre entradas (argumentos) e saídas (valores retornados). O return explicita qual é essa saída e, além disso, encerra imediatamente a execução da função no momento em que é encontrado. Se não houver nenhuma instrução return no corpo da função, ou se for utilizado apenas return sem valor explícito, o Python devolve automaticamente o valor especial None.
Um dos erros mais frequentes entre iniciantes é confundir print com return. Embora ambos possam produzir algo visível na tela, eles têm naturezas completamente distintas. Enquanto o print apenas envia uma representação do valor para a saída padrão (normalmente o terminal), o return produz um valor que pode ser armazenado em uma variável, reutilizado em cálculos ou passado como argumento para outra função.
Vejamos um exemplo de uma função que calcula a raiz quadrada de um número:
def raiz1(x):
fx = x**0.5
print(fx)
y = raiz1(100)
print(y)10.0
None
Nesse caso, a função imprime o valor calculado, mas não o retorna. Como não há return, o valor devolvido por raiz1 é None. Assim, a variável y passa a armazenar None, e é exatamente isso que será impresso na última linha. Se substituirmos a instrução de impressão pela instrução de retorno, o comportamento muda estruturalmente:
def raiz2(x):
fx = x**0.5
return fx
y = raiz2(100)
print(y)10.0
Agora a função devolve o valor numérico calculado. A variável y armazena esse valor, que pode ser reutilizado em outras operações. A função passa a se comportar como uma transformação matemática: recebe um número e devolve outro. Essa distinção torna-se ainda mais clara quando tentamos usar o resultado em outra operação numérica:
z = raiz1(100) + 5
print(z)10.0
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[17], line 1 ----> 1 z = raiz1(100) + 5 2 print(z) TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
O código acima gera erro porque raiz1(100) devolve None, e não é possível somar None com um número. Já no caso da função que utiliza a instrução de retorno, a operação funciona normalmente:
z = raiz2(100) + 5
print(z)15.0
Em programação científica e análise de dados, quase sempre desejamos funções que retornem valores, pois esses resultados serão encadeados em outras operações, armazenados em estruturas de dados ou utilizados em rotinas subsequentes. A impressão na tela é útil para depuração ou visualização intermediária, mas não substitui o mecanismo formal de retorno. Em termos conceituais, a diferença pode ser resumida da seguinte forma: funções que usam apenas print produzem efeitos; funções que utilizam return produzem valores.
6.5.1 Expectativa de vida de variáveis dentro da função
Quando você cria uma variável dentro de uma função ela é local, ou seja, só existe dentro da própria função. Por exemplo:
def concat_strings(str1,str2):
texto_concatenado = str1 + ' ' + str2
print(texto_concatenado)
texto1 = 'Que a força esteja com você,'
texto2 = 'jovem Padawan.'
concat_strings(texto1,texto2)Que a força esteja com você, jovem Padawan.
Essa função recebe dois argumentos, concatena-os e exibe o resultado em uma única linha de texto. No entanto, assim que a função é encerrada, a variável texto_concatenado é deletada. O que acontece se tentarmos acessá-la?
print(texto_concatenado)--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[20], line 1 ----> 1 print(texto_concatenado) NameError: name 'texto_concatenado' is not defined
Com a base conceitual de definição, argumentos e retorno consolidada, podemos agora aplicar esses conceitos em um exemplo mais substancial. A aplicação do Teorema Central do Limite mostrará como estruturar uma rotina que recebe distribuições e parâmetros como argumentos e retorna medidas agregadas de interesse. Essa transição marca o passo do conceito para a aplicação.
6.6 Aplicação: Teorema Central do Limite
O primeiro passo para replicarmos, na forma de uma função, a aplicação do Teorema Central do Limite da aula anterior é definir uma função que recebe como argumentos a distribuição da varíavel aleatória X, o intervalo no qual a variável está definida, o número de sorteios que faremos em cada amostra e o número de amostras realizadas. Para tal, definiremos a função func_tcl que recebe como argumentos nomeados dist (função de distribuição da variável X), intervalo (suporte da variável X), n (número de sorteios por amostra) e samples (número de amostras).
Apesar de definir valores-padrão válidos para intervalo, n e samples, é atribuído ao argumento dist um valor padrão igual a None. Isso permite ao usuário ter controle direto sobre a função de distribuição utilizada para sortear X e evitar tornar a nossa função uma caixa preta. Assim, caso o usuário esqueça de definir uma distribuição válida, a função retorna um erro.
Caso defina uma função de distribuição válida, a função func_tcl avança e passar a realizar a sequência de sorteios de cada amostra e a média dentro de cada amostra através de um loop do tipo for.
# definição da função
def func_tcl(dist=None,intervalo=(0,1),n=100, samples=10):
if dist == None:
print('Você esqueceu de carregar uma função que defina a distribuição de X.')
else:
means = []
for j in range(0,samples):
x_func_tcl = dist(intervalo[0],intervalo[1],n)
mean_x = sum(x_func_tcl)/len(x_func_tcl)
means.append(mean_x)
mean_of_means = sum(means)/len(means)
return mean_of_means
# comando para garantir a reprodutibilidade
np.random.seed(1)
# uso da função
func_tcl(dist=np.random.uniform,intervalo=(-40,40),n=50,samples=10)np.float64(0.662169619365357)
Maravilha, agora a função nos retorna apenas o que é do nosso interesse: a média das médias. Para chegar, finalmente, no mesmo resultado da aula passada basta colocar essa função dentro de um loop.
expoentes = [1,2,3,4,5,6,7,8,9,10]
Y = [2**exp for exp in expoentes]
means_of_means = []
np.random.seed(1)
for y in Y:
mean_of_means = func_tcl(dist=np.random.uniform,intervalo=(-40,40),n=100,samples=y)
means_of_means.append(mean_of_means)for m in means_of_means:
print(np.round(m,2))-1.17
0.92
0.37
-0.25
0.31
-0.32
0.23
-0.15
-0.06
0.03
Voilá! Como exercício para casa, tente fazer alterações nessa função de modo que ela receba não um valor com o número de amostras, mas uma lista de números de amostras. A função deve cuspir como resultado a lista means_of_means e não apenas o valor mean_of_means.
Após a aplicação prática, é oportuno discutir como documentar funções de forma que qualquer usuário (inclusive você no futuro) possa compreender rapidamente o propósito, argumentos e comportamento de uma rotina implementada. A próxima seção introduz docstrings como forma padrão de documentação no Python.
6.7 Documentação
Uma docstring é uma string no início de uma função, definida pelo usuário, que serve como documentação do que a função faz. A docstring vem logo depois da primeira linha que define a função e é delimitada por aspas triplas, o que permite que a string se estenda por várias linhas, como vocês devem se lembrar. Vamos criar como exemplo uma função que printa o tipo do objeto que é passado como argumento:
def imprime_tipo(x):
'''
Função criada para a matéria EAE1106 - Métodos Computacionais para Economia
Objetivo: função simples que imprime o tipo do objeto recebido como argumento.
'''
print(type(x))Embora opcional, a documentação é uma boa prática de programação. A menos que você consiga se lembrar qual foi o cardápio do bandejão na semana passada, sempre documente seu código. Podemos acessar a documentação de determinada função, nativa ou personalizada, utilizando o atributo __doc__.
print(imprime_tipo.__doc__)
Função criada para a matéria EAE1106 - Métodos Computacionais para Economia
Objetivo: função simples que imprime o tipo do objeto recebido como argumento.
# documentação da função nativa len()
print(len.__doc__)Return the number of items in a container.
# documentação da função uniform() dentro do pacote NumPy
print(np.random.uniform.__doc__)
uniform(low=0.0, high=1.0, size=None)
Draw samples from a uniform distribution.
Samples are uniformly distributed over the half-open interval
``[low, high)`` (includes low, but excludes high). In other words,
any value within the given interval is equally likely to be drawn
by `uniform`.
.. note::
New code should use the `~numpy.random.Generator.uniform`
method of a `~numpy.random.Generator` instance instead;
please see the :ref:`random-quick-start`.
Parameters
----------
low : float or array_like of floats, optional
Lower boundary of the output interval. All values generated will be
greater than or equal to low. The default value is 0.
high : float or array_like of floats
Upper boundary of the output interval. All values generated will be
less than or equal to high. The high limit may be included in the
returned array of floats due to floating-point rounding in the
equation ``low + (high-low) * random_sample()``. The default value
is 1.0.
size : int or tuple of ints, optional
Output shape. If the given shape is, e.g., ``(m, n, k)``, then
``m * n * k`` samples are drawn. If size is ``None`` (default),
a single value is returned if ``low`` and ``high`` are both scalars.
Otherwise, ``np.broadcast(low, high).size`` samples are drawn.
Returns
-------
out : ndarray or scalar
Drawn samples from the parameterized uniform distribution.
See Also
--------
randint : Discrete uniform distribution, yielding integers.
random_integers : Discrete uniform distribution over the closed
interval ``[low, high]``.
random_sample : Floats uniformly distributed over ``[0, 1)``.
random : Alias for `random_sample`.
rand : Convenience function that accepts dimensions as input, e.g.,
``rand(2,2)`` would generate a 2-by-2 array of floats,
uniformly distributed over ``[0, 1)``.
random.Generator.uniform: which should be used for new code.
Notes
-----
The probability density function of the uniform distribution is
.. math:: p(x) = \frac{1}{b - a}
anywhere within the interval ``[a, b)``, and zero elsewhere.
When ``high`` == ``low``, values of ``low`` will be returned.
If ``high`` < ``low``, the results are officially undefined
and may eventually raise an error, i.e. do not rely on this
function to behave when passed arguments satisfying that
inequality condition. The ``high`` limit may be included in the
returned array of floats due to floating-point rounding in the
equation ``low + (high-low) * random_sample()``. For example:
>>> x = np.float32(5*0.99999999)
>>> x
np.float32(5.0)
Examples
--------
Draw samples from the distribution:
>>> s = np.random.uniform(-1,0,1000)
All values are within the given interval:
>>> np.all(s >= -1)
True
>>> np.all(s < 0)
True
Display the histogram of the samples, along with the
probability density function:
>>> import matplotlib.pyplot as plt
>>> count, bins, ignored = plt.hist(s, 15, density=True)
>>> plt.plot(bins, np.ones_like(bins), linewidth=2, color='r')
>>> plt.show()
Note do exemplo acima que uma docstring pode conter uma descrição detalhada do funcionamento de uma função, inclusive com exemplos de aplicação. Legal, né? Esse tipo de documentação também está disponível para as bibliotecas como um todo. Isso pode nos ajudar, por exemplo, a conhecer o conteúdo de uma determinada biblioteca.
# documentação do NumPy
print(np.__doc__)
NumPy
=====
Provides
1. An array object of arbitrary homogeneous items
2. Fast mathematical operations over arrays
3. Linear Algebra, Fourier Transforms, Random Number Generation
How to use the documentation
----------------------------
Documentation is available in two forms: docstrings provided
with the code, and a loose standing reference guide, available from
`the NumPy homepage <https://numpy.org>`_.
We recommend exploring the docstrings using
`IPython <https://ipython.org>`_, an advanced Python shell with
TAB-completion and introspection capabilities. See below for further
instructions.
The docstring examples assume that `numpy` has been imported as ``np``::
>>> import numpy as np
Code snippets are indicated by three greater-than signs::
>>> x = 42
>>> x = x + 1
Use the built-in ``help`` function to view a function's docstring::
>>> help(np.sort)
... # doctest: +SKIP
For some objects, ``np.info(obj)`` may provide additional help. This is
particularly true if you see the line "Help on ufunc object:" at the top
of the help() page. Ufuncs are implemented in C, not Python, for speed.
The native Python help() does not know how to view their help, but our
np.info() function does.
Available subpackages
---------------------
lib
Basic functions used by several sub-packages.
random
Core Random Tools
linalg
Core Linear Algebra Tools
fft
Core FFT routines
polynomial
Polynomial tools
testing
NumPy testing tools
distutils
Enhancements to distutils with support for
Fortran compilers support and more (for Python <= 3.11)
Utilities
---------
test
Run numpy unittests
show_config
Show numpy build configuration
__version__
NumPy version string
Viewing documentation using IPython
-----------------------------------
Start IPython and import `numpy` usually under the alias ``np``: `import
numpy as np`. Then, directly past or use the ``%cpaste`` magic to paste
examples into the shell. To see which functions are available in `numpy`,
type ``np.<TAB>`` (where ``<TAB>`` refers to the TAB key), or use
``np.*cos*?<ENTER>`` (where ``<ENTER>`` refers to the ENTER key) to narrow
down the list. To view the docstring for a function, use
``np.cos?<ENTER>`` (to view the docstring) and ``np.cos??<ENTER>`` (to view
the source code).
Copies vs. in-place operation
-----------------------------
Most of the functions in `numpy` return a copy of the array argument
(e.g., `np.sort`). In-place versions of these functions are often
available as array methods, i.e. ``x = np.array([1,2,3]); x.sort()``.
Exceptions to this rule are documented.
Podemos usar sempre esse atalho caso desejemos conhecer as funcionalidades que uma biblioteca guarda por trás de suas cortinas!
6.8 Funções anônimas
Uma vez familiarizado com a definição e documentação de funções nomeadas, surge a possibilidade de trabalhar com funções que não possuem nome explícito, as chamadas funções anônimas. Enquanto as funções normais são definidas usando a palavra-chave def no Python, as funções anônimas são definidas usando a palavra-chave lambda. Por essa razão, funções anônimas também são chamadas de funções lambda. A estrutura usual de uma função lambda é a seguinte:
lambda <argumentos>: <expressão>Usualmente, utilizamos uma função lambda porque precisamos de uma função rápida por um determinado período de tempo e/ou quando utilizamos técnicas mais poderosas que possuem funções como argumento, como filter e map. Vou fazer um exemplo com cada uma delas.
6.8.1 filter
A função filter() em Python recebe uma função e uma lista como argumentos. A função é chamada com todos os itens da lista e uma nova lista é retornada contendo itens para os quais a função avalia True. Aqui está um exemplo de uso da função para filtrar apenas números pares de uma lista.
my_list = [1, 5, 4, 6, 8, 11, 3, 12]
new_list = list(filter(lambda x: (x%2 == 0) , my_list))
print(new_list)[4, 6, 8, 12]
6.8.2 map
A função map() em Python recebe uma função e uma lista. A função é chamada com todos os itens da lista e uma nova lista é retornada contendo os itens retornados por essa função para cada item. Aqui está um exemplo de uso da função map() para dobrar todos os itens em uma lista.
my_list = [1, 5, 4, 6, 8, 11, 3, 12]
new_list = list(map(lambda x: x * 2 , my_list))
print(new_list)[2, 10, 8, 12, 16, 22, 6, 24]
6.9 Conclusão
Funções representam o principal mecanismo de organização da lógica de um programa. Elas permitem decompor problemas complexos em partes menores, reutilizar código, reduzir redundância e tornar explícita a relação entre entradas e saídas. Do ponto de vista computacional, introduzem o conceito de escopo, estruturam o fluxo de execução e tornam o código mais legível e testável.
No contexto da Economia aplicada, funções são particularmente relevantes porque grande parte do trabalho empírico envolve transformar dados em resultados por meio de rotinas repetidas: cálculo de estatísticas, limpeza de bases, simulações ou estimações. Escrever essas rotinas como funções facilita replicação, comparação de cenários e organização de projetos.
Nos próximos capítulos, ao utilizar bibliotecas como NumPy e Pandas, veremos que praticamente todas as operações são estruturadas como chamadas de funções ou métodos. A compreensão sólida do que é uma função é condição necessária para avançar com segurança em programação numérica e análise de dados.
6.10 Exercícios
- Escreva uma função chamada triângulo que recebe uma string como argumento e retorna como output um triângulo retângulo de determinada altura, usando cópias da string, como no exemplo:
triangle('L', 5)L\
LL\
LLL\
LLLL\
LLLLL\
Defina uma função que retorne a quantidade de dinheiro em uma conta corrente após uma transação. Os dois parâmetros para essa função são saldo e transação. Ambos serão argumentos inteiros. O saldo é quanto dinheiro existe atualmente na conta, e a transação quanto adicionar ou subtrair da conta corrente (baseado se é um inteiro positivo ou negativo). Note que essa operação é mais complicada que apenas
saldo + transacao. Isso porque se a transação for negativa (saída de dinheiro da conta) e em valor absoluto maior do que o saldo existente, a transação deve ser ignorada e o valor original de saldo retornado pela função.Escreva uma função chamada
sum_listque recebe uma lista de números e retorna um dicionário com duas chaves e dois valores.- O primeiro par chave-valor deve associar à chave
somao valor numérico da soma de todos os elementos. - O segundo par chave-valor deve associar à chave
elementoso número de elementos somados.
- O primeiro par chave-valor deve associar à chave
Defina uma função que recebe uma temperatura em Celsius e retorna o valor convertido para Fahrenheit com duas casas decimais. Agora crie uma segunda função, mais genérica, que receba como primeiro argumento
CtoFouFtoCe como segundo argumento a temperatura. Essa segunda função deve ser capaz de converter as escalas de temperatura em ambas as direções. Por padrão, o primeiro argumento deve ser definido comoCtoF.