Python intermediário - II¶
Funções¶
O conceito de “função” é conhecido por nós como algo que produz uma saída (output) a partir de uma entrada (input). Matematicamente, podemos representar este processo como \(y= f(x)\). Por exemplo, uma impressora jato de tinta ao receber uma folha de papel em branco (\(x\)) aciona seus mecanismos de impressão (\(f\)) e produz uma folha impressa (\(y\)).
Em Python, funções são utilizadas praticamente da mesma forma, em que zero ou mais entradas retornam uma saída correspondente. Funções são fundamentais para organizar e reutilizar código, sempre que uma tarefa tenha de ser executada repetidamente. Toda função é um objeto de primeira classe. Isto essencialmente significa que ela pode ser: i) atribuída a uma variável ou elemento em uma estrutura de dado; ii) passada como argumento para outra função; iii) retornada como resultado de uma função.
Tipos de funções¶
Neste curso, separaremos as funções em três grupos:
predefinidas (built-in functions): aquelas pré-existentes no core da linguagem ou em outros módulos;
regulares (user-defined functions): aquelas definidas por você que possuem um nome definido (podem ser chamadas de UDFs);
anônimas (lambdas): funções, em geral, criadas por você que não exigem um nome.
Note
Funções lambda
, a rigor, são caracterizadas como uma user-defined function e podem ser incorporadas juntamente com as funções nominadas por def
em apenas um grupo. Apesar disso, aqui neste curso, tratamos funções anônimas em separado apenas para evitar subclassificações.
Funções predefinidas¶
Exploraremos exemplos com funções built-in do core Python.
Exemplos: funções predefinidas do core da linguagem.
# built-in functions
hex(1234),bin(345),round(12.3456,3)
('0x4d2', '0b101011001', 12.346)
Discussão:
hex
converte um número para hexadecimal, indicado pelo prefixo0x
.bin
converte um número para binário, indicado pelo prefixo0b
.round(x,n)
arredonda um númerox
emn
dígitos de precisão. Sen < 0
, retorna0.0
.
# built-in function
for i in range(600,700,10):
print(chr(i),end=',')
ɘ,ɢ,ɬ,ɶ,ʀ,ʊ,ʔ,ʞ,ʨ,ʲ,
Discussão:
chr
retorna o caracter Unicode correspondente ao número inteiro passado, desde que esteja no intervalo [0,1114111].
Exemplo: somando números em uma lista.
sum(range(100)), sum([])
(4950, 0)
Discussão:
sum
é uma função predefinida aplicável a sequências iteráveis.Se o iterável for vazio,
sum
retorna zero.No exemplo anterior, somamos os números de 0 a 100 e aplicamos a função a uma lista vazia.
Exemplo: número de elementos em iteráveis.
# no. de elementos
len([1,4,5])
3
# conta keys
len({'a':1,'b':2})
2
# lembre da unicidade
# 3 elementos no conjunto
len({1,3,1,2})
3
# conta caracteres
len('nome')
4
Funções regulares¶
Funções regulares, como dissemos, possuem um nome definido pelo usuário. Vejamos alguns exemplos.
Exemplos: funções regulares e uso de funções.
def cm_to_inch(x):
"""converte número de centímetros para polegadas"""
return 2.45*x
Comentários:
Funções regulares são declaradas com a keyword
def
e valores de retorno com a keywordreturn
.
# chamada simples
cm_to_inch(23)
56.35
# atribuindo em objeto
cmi = cm_to_inch
# usando como argumento de outra função
def fn(n,f):
"""Calcula f(n) dados n e uma função f."""
return f(n)
fn(2,cm_to_inch)
4.9
# docstring da função
fn.__doc__
'Calcula f(n) dados n e uma função f.'
Parâmetros de funções podem assumir um argumento padrão que pode ser modificado sempre que necessário.
Exemplos:
# 'BEGIN' é valor padrão
def line(title='BEGIN'):
print(title.center(20,'-'))
# especificação não necessária
line()
-------BEGIN--------
# alterando padrão
line('HEAD')
--------HEAD--------
Exemplo: função com argumentos posicionais.
# função com 2 argumentos posicionais
def upper_len(s,cut):
if isinstance(s,str): # checa se é str
print(':: ' + s.upper()[:cut],sep=',')
else:
pass # não faz nada
from random import randint
abc = ['alfa', 'bravO', 'chARlie', 230, 111.222]
# fatiamento aleatório
for s in abc:
upper_len(s,randint(0,7))
:: ALF
:: BRAVO
:: CHARLI
Comentários:
Esta função aceita strings, formatam-nas em maiúsculas e imprime-as fatiadas até o índice
cut
, determinado aleatoriamente.Note que nada é impresso para entradas que são números.
A função possui dois argumentos posicionais, isto é que obedecem às posições especificadas.
Se as posições dos argumentos forem alteradas, o sentido da função muda.
# Nada faz porque o primeiro
# parâmetro deixou de ser str
for s in abc:
upper_len(randint(1,3),s)
Exemplo: especificando argumentos com keywords.
# declaração com 3 keywords
def triple(x=0,y=0,z=0):
return x*y*z
# padrão
triple()
0
# sem declarar keywords
triple(2,3,4)
24
# declarando uma keyword
triple(3,4,z=4)
48
# erro!
triple(x=3,4,4)
File "<ipython-input-23-e785690d4856>", line 2
triple(x=3,4,4)
^
SyntaxError: positional argument follows keyword argument
Discussão:
Argumentos com keyword devem vir após os argumentos posicionais, se houver algum.
# declaração com 1 posicional e 2 keywords
def triple_2(x,y=0,z=0):
return x*y*z
triple_2(2,y=5,z=3)
30
triple_2(2,5,z=3)
30
# declaração com 2 posicionais e 1 keyword
def triple_3(x,y,z=0):
return x*y*z
triple_3(3,1,2)
6
triple_3(3,1,z=2)
6
Escopos¶
Funções podem acessar variáveis em dois escopos: global e local. O escopo global é aquele que está fora do escopo da função, enquanto o local é aquele determinado pela função.
Exemplo: atribuindo variáveis global e localmente
a = 0 # global
def test_vars(a):
b = 1 # local
return a + b
print(test_vars(a))
print(b) # erro! b é local
1
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-30-4b9fde101a12> in <module>
5
6 print(test_vars(a))
----> 7 print(b) # erro! b é local
NameError: name 'b' is not defined
Comentários:
A variável
a
é definida globalmente e foi utilizada como argumento detest_vars
;A variável
b
é definida localmente e, portanto, não pode ser acessada fora do escopo da função;Um erro de “variável indefinida” é lançado por
print(b)
, visto que o escopo global não reconhece a variávelb
.
def test_vars(a):
global c
c = 1 # global
return a + c
print(test_vars(a))
print(c) # ok! c é global
1
1
Comentários:
Neste caso, a
c
foi atribuído um valor no escopo local, porém ela foi declarada como global.Para tornar uma variável dentro do escopo de uma função como global, usa-se a keyword
global
.A impressão do valor de
c
no escopo global não retorna erro pela explicação anterior.
Warning
O uso indiscriminado de global
não é encorajado. Quando a densidade do uso de global
está alta, recomenda-se partir para uma abordagem de orientação a objetos e usar classes.
Funções anônimas¶
Uma função anônima em Python consiste em uma função cujo nome não é explicitamente definido e que pode ser criada em apenas uma linha de código para executar uma tarefa específica.
Funções anônimas são baseadas na palavra-chave lambda. Este nome tem inspiração em uma área da ciência da computação chamada de cálculo-\(\lambda\).
Uma função anônima tem a seguinte forma:
lambda lista_de_parâmetros: expressão
Embora funções anônimas possam ser chamadas isoladamente, seu melhor uso é como argumento de uma função.
Vejamos alguns exemplos.
Exemplo:
square = lambda x: x**2
[square(x) for x in [2,3,4]]
[4, 9, 16]
Comentários:
square
é um objetofunction
(verifique comtype(square)
.Esta função anônima eleva um número passado ao quadrado.
Exemplo:
def op_to_list(lista,f):
return [f(x) for x in lista]
op_to_list([2,3,4],lambda x: x**2)
[4, 9, 16]
Discussão:
Esta construção produz um resultado equivalente ao anterior.
Neste exemplo, a função anônima é passada como argumento para ser aplicada à lista.
Comentários:
A construção anterior nos dá liberdade para criar funções com propósitos diversos.
Exemplo:
# f(x) = x**3 - 4
op_to_list([2,3,4], lambda x: x**3 - 4)
[4, 23, 60]
Exemplo:
# separa nome e sobrenome,
# e cria um user
op_to_list(['Aldo Bermudes',
'Vicário Sempernaum',
'Sebastian Folcher'],
lambda x: (x.split(' '),
x.lower().replace(' ','.')))
[(['Aldo', 'Bermudes'], 'aldo.bermudes'),
(['Vicário', 'Sempernaum'], 'vicário.sempernaum'),
(['Sebastian', 'Folcher'], 'sebastian.folcher')]
Comentários:
O abuso de lambdas pode ser prejudicial à legibilidade de código.
No exemplo acima, uma lista de nomes é quebrada em nome e sobrenome e um nome de usuário de e-mail hipotético é construído para cada elemento da lista.
Exemplo:
frutas = ['melão','melancia','abacate',
'morango','romã','banana']
sorted(frutas,key=lambda x: x[::-1])
['melancia', 'banana', 'abacate', 'morango', 'melão', 'romã']
Discussão:
x[::-1]
faz um swap na string;A função lambda passada como key permite que os elementos da lista sejam ordenados com base na soletração invertida delas.
map
¶
A função map
serve para construir uma função que será aplicada a todos os elementos de uma sequência. Seu uso é da seguinte forma:
map(funcao,sequencia)
A função opt_to_list
, criada na seção anterior, na verdade, poderia ter sido substituída por map
, visto que map
“aplica” uma regra a todos os elementos da sequência.
Exemplo:
list(map(lambda x: x**2,[2,3,4]))
[4, 9, 16]
A conversão de map
para um list
é importante, pois o uso solitário de map
produzirá uma resposta abstrata.
map(lambda x: x**2,[2,3,4])
<map at 0x7f937d09e460>
Exemplo:
frutas = ['melão','melancia','abacate',
'morango','romã','banana']
tuple(map(lambda x: x[::-1],frutas))
('oãlem', 'aicnalem', 'etacaba', 'ognarom', 'ãmor', 'ananab')
Comentários:
O
map
é utilizado para realizar um swap nas strings.Em seguida, realizamos uma conversão do
map
em um tupla.
filter
¶
Podemos aplicar uma espécie de “filtro” para valores usando a função filter
. No caso anterior, digamos que valores acima de 7 sejam inseridos erroneamente no gerador de números (lembre-se que no sistema sanguíneo ABO, consideramos um dict cujo valor das chaves é no máximo 7). Podemos, ainda assim, filtrar a lista para coletar apenas valores menores do que 7. Para tanto, definimos uma função lambda com este propósito.
from random import randint
nums = []
for _ in range(15):
nums.append(randint(0,30))
f = filter(lambda x: x < 20,nums)
list(f)
[4, 18, 4, 19, 15, 9, 8, 6, 5]
Funções de ordem superior¶
A partir da introdução de compreensões de lista, map
e filter
passaram a ser menos importantes, embora ainda estejam disponíveis na linguagem. Juntamente com reduce
e apply
, map
e filter
são funções de ordem superior (aquelas que recebem funções como argumento ou retornam funções como resultado). Todas elas são menos frequentes em Python 3 devido à aparição de métodos mais “modernos” de realizar o mesmo.
args
e kwargs
¶
Imagine que, por alguma razão específica, você precise construir uma função com um número arbitrário de argumentos. Este tipo de situação ocorre quando o usuário tem ampla liberdade para configurar algo.
Vimos no capítulo anterior o desempacotamento por star expression. Para construir funções como as que queremos, podemos usar uma abordagem similar usando as star expressions *args
e **kwargs
.
Um resumo da aplicação delas é o seguinte:
*args
é uma tupla e utilizada para substituir um número arbitrário de argumentos posicionais.**kwargs
é um dicionário e utilizada para substituir um número arbitrário de keywords e seus valores correspondentes.
A melhor forma de compreendê-las é por meio de exemplos.
Exemplo: função que aceita um número arbitrário de argumentos de entrada.
def media_arit(*vals):
return sum(vals)/len(vals)
print(media_arit(1,2))
print(media_arit(1,2,4,5))
print(media_arit(1,2,4,5,7,9,10))
1.5
3.0
5.428571428571429
Discussão:
*vals
recebe, em cada caso, os argumentos de entrada. No primeiro caso, 2; no segundo, 4; no terceiro, 7.
Exemplo: equação de combinação linear em \(\mathbb{R}^n\) com coeficientes arbitrários.
from IPython.display import display as dpl, Math
def comb_lin(*args):
l = []
for i,c in enumerate(list(args)):
l.append(str(c) + 'x_' + str(i+1))
final = '$v = ' + ' + '.join(l) + '$'
return final
v2 = comb_lin(1.4,4)
dpl(Math(v2))
v4 = comb_lin(2,34,12,65.43)
dpl(Math(v4))
Discussão:
A lista de coeficientes é percorrida com agregação dos termos \(x_i\), para cada coordenada do espaço n-dimensional.
dpl(Math(v4)
renderiza a equação.
Exemplo: coleta de valores médios de áreas das superfícies radiculares de dentes humanos (em milímetros quadrados) por demanda.
# dente: (area inf, area sup)
# Ver: https://bit.ly/3iT9tsA, p.37, Quadro 2.1
#dentes = {'Incisivo central':(170,230),
# 'Incisivo lateral':(194,200),
# 'Canino':(270,282),
# 'Primeiro molar':(475,533)}
def med_area_dent(**kwargs):
for k,v in kwargs.items():
print(f'Dente: {k} | Área inf: {v[0]} mm2 | Área sup: {v[1]} mm2 ')
med_area_dent(incisivo_cent=(160,200),
incisivo_lat=(194,200))
Dente: incisivo_cent | Área inf: 160 mm2 | Área sup: 200 mm2
Dente: incisivo_lat | Área inf: 194 mm2 | Área sup: 200 mm2
med_area_dent(IC=(160,200),C=(270,882),PM=(475,533))
Dente: IC | Área inf: 160 mm2 | Área sup: 200 mm2
Dente: C | Área inf: 270 mm2 | Área sup: 882 mm2
Dente: PM | Área inf: 475 mm2 | Área sup: 533 mm2
Discussão:
**kwargs
admite a entrada de um dicionário com tamanho variável. Isto é, chaves e valores quaisquer.Nestes exemplos, imprimimos os valores médidos de áreas dos radiculares.
Comentários:
Cabe enfatizar que as
keywords
podem ter nomes diferentes.
any
e all
¶
As funções predefinidas any
e all
são chamadas de “redutoras”, visto que têm um papel relacionado à filtragem de dados. Elas são aplicáveis a qualquer sequência. Podemos entendê-las como segue:
any
: retornaTrue
se todo elemento do objeto iterável for avaliado com uma condição verdadeira.all
: retornaTrue
se qualquer elemento do objeto iterável for avaliado com uma condição verdadeira.
Exemplo:
x = [2,-1,3,4]
all(list(map(lambda x: x < 0,x)))
False
any(list(map(lambda x: x < 0,x)))
True
x = [2,1,3,4]
all(list(map(lambda x: x > 0,x)))
True
Exercícios aplicados¶
Exercício. A FEBRABAN (Federação Brasileira dos Bancos) utiliza um padrão de 52 caracteres (45 dígitos + 3 pontos + 4 espaços) para numeração de boletos bancários na forma
BBBML.LLLLC LLLLL.LLLLD LLLLL.LLLLE G FFFFVVVVVVVVVV
onde
B é dígito do código verificador de banco
M é dígito do código verificador de moeda
C é dígito do código identificador de campo
D é dígito do código identificador de campo
E é dígito do código identificador de campo
F é dígito do fator de vencimento
G é dígito do código verificador geral
V é dígito do valor do documento
Use essas informações para criar uma função que gera códigos de boleto hipotéticos e retorne o código do banco, tipo de moeda e valor do documento como múltiplas respostas. Assuma que L é sempre igual ao dígito zero.
Dica: use random.randint
.