Complementando a aula passada será explicado melhor como funciona a função format()
e str.format()
começando pela última.
A função str.format()
converte os campos envolvos por chaves pelos argumentos passados a ela. Caso não seja especificado o nome ou posição dos argumentos a troca de chaves por dados é feita na ordem:
In [1]:
'{} + {} = {}'.format(10, 10, 20)
Out[1]:
Porém podemos especificar explicitamente as posições que queremos substituir:
In [2]:
'{0} + {1} = {2}'.format(10, 10, 20) # esse é o padrão
Out[2]:
Podemos repetir um único argumento:
In [3]:
'{0} + {0} = {1}'.format(10, 20)
Out[3]:
Informar uma ordem arbritária:
In [4]:
'{1} + {0} = {2}'.format(30, 20, 10) # evite fazer isso para não causar confusão
Out[4]:
Também é possível dar nomes a esses campos:
In [5]:
string = '{cidade} é muito bonito(a) durante o(a) {estação}'
string.format(cidade='Bruxelas', estação='Inverno')
Out[5]:
Também podemos formatar números:
In [6]:
'O total é R${0}'.format(59.8912313)
Out[6]:
In [7]:
'O total é R${0:.2f}'.format(59.8912313)
Out[7]:
In [8]:
'A porcentagem é de {0:.2%}'.format(0.8912313)
Out[8]:
format()
também permite alinhar elementos dentro de um espaço de 10 caracteres:
In [9]:
'{0:<10} | {1:<10} | {2:<10}'.format('Qtd.', 'Cor', 'Valor') # alinhado à esquerda
Out[9]:
In [10]:
'{0:>10} | {1:>10} | {2:>10}'.format('Qtd.', 'Cor', 'Valor') # alinhado à direita
Out[10]:
O tamanho do espaço pode ser diferente nos elementos:
In [11]:
'{0:^6} | {1:^9} | {2:^10}'.format('Qtd.', 'Cor', 'Valor') # centralizado
Out[11]:
Também é possível mudar o caracter de preenchimento do espaço dado de caracter em branco para algum outro:
In [12]:
'{0:+^6} | {1:=^9} | {2:-^10}'.format('Qtd.', 'Cor', 'Valor') # centralizado
Out[12]:
In [13]:
'{0:+^6} | {1:=^9} | {2:-^10}'.format('Qtd.', 'Cor', 'Valor') # centralizado
Out[13]:
Também podemos armazenar format e reaplicá-los para valores diferentes:
In [14]:
formato_tabela = '{0:^6} | {1:^9} | {2:^10}'
formato_tabela
Out[14]:
In [15]:
produtos = [
(2, 'Amarelo', 18.50),
(5, 'Verde', 48.50),
(2, 'Azul', 78.50),
]
produtos
Out[15]:
In [16]:
print(formato_tabela.format('Qtd.', 'Cor', 'Valor R$'))
for qtd, cor, valor in produtos:
print(formato_tabela.format(qtd, cor, valor))
Também é possível especificar o tipo dos dados:
In [17]:
'{0:e} {0:f} {0:%}'.format(.0000031)
Out[17]:
Os formatos possíveis são:
Também é possível formatar valores usando a função embutida format()
:
In [18]:
import math
format(math.pi, '6.3f')
Out[18]:
In [19]:
format('Python', '.<12')
Out[19]:
In [20]:
format('Python', '.>12')
Out[20]:
In [21]:
format('Python', '.^12')
Out[21]:
Diferentemente da função str.format()
a função embutida format()
não permite substituição de caracteres usando chaves.
Para saber mais sobre o assunto consulte a documentação e este documento feito pelo Luciano Ramalho que explica detalhadamente como usar essa função.
Na aula passada vimos outros tipos básicos: números e sequência.
Agora vamos falar sobre conjuntos.
Conjunto, ou set, é uma ferramenta subutilizada do Python, tanto que muitos cursos introdutórios não passam abordam esse assunto.
Set vem da teoria de conjuntos da matemática. Um conjunto não permite a existência de elementos iguais dentro de si, por conta disso é muito utilizado para remover repetições:
In [22]:
l = ['spam', 'spam', 'eggs', 'spam']
l
Out[22]:
In [23]:
set(l)
Out[23]:
Como podemos ver a sintaxe de set - {1}, {1, 2}, etc. - se parece exatamente com a notação matemática, com exceção que não existe uma notação para set vazio. Caso você precise criar um conjunto vazio use: set()
.
In [24]:
A = set()
len(a)
Vale lembrar que conjuntos também se comportam como sequências, por conta disso é possível usar neles as funções que aprendemos anteriormente:
In [25]:
A = {5, 4, 3, 3, 2, 10}
A
Out[25]:
In [26]:
len(A)
Out[26]:
In [27]:
sum(A)
Out[27]:
In [28]:
max(A)
Out[28]:
In [29]:
min(A)
Out[29]:
Um ponto importante a se observar é que conjunto não mantém a ordem dos elementos:
In [30]:
A = {4, 5, 1, 3, 4, 5, 7}
A # ordem diferente da declarada!
Out[30]:
Por isso não é possível acessar os elementos pela posição:
In [31]:
A[0]
É possível acessar seus elementos iterando o set:
In [32]:
for num in A:
print(num)
Ou convertendo-o para tupla ou lista:
In [33]:
tuple(A)
Out[33]:
In [34]:
tuple(A)[0]
Out[34]:
In [35]:
list(A)
Out[35]:
In [36]:
list(A)[-1]
Out[36]:
Assim como listas os conjuntos também possuem um mecanismo simplificado para construir conjuntos o set comprehension
:
In [37]:
{letra for letra in 'abrakadabraalakazam'}
Out[37]:
In [38]:
{numero for numero in range(30) if numero % 3 != 0}
Out[38]:
Outra característica importante de conjuntos é que eles realizam verificações de pertencimento de forma muito mais eficiente.
Para verificar se um elemento está presente em um conjunto usamos o mesmo operador in
que vimos para lista, tuplas e string:
In [4]:
A = {-10, 0, 10, 20, 30, 40}
A
Out[4]:
In [5]:
5 in A
Out[5]:
In [6]:
-10 in A
Out[6]:
Para demonstrar que conjuntos verificam se um elemento pertence em uma coleção de maneira mais rápida isso vamos usar o módulo timeit que oferece uma maneira simples de contar o tempo de execução de código Python.
O módulo timeit
possui uma função timeit(stmt='pass', setup='pass', number=1000000, ...)
que executa um setup
e um código
(stmt) uma dada quantidade de vezes e contabiliza o tempo levado para executar o código (o tempo de rodar o setup não é incluído). O código e o setup devem ser passados como strings.
In [39]:
import timeit
tempo = timeit.timeit('[math.exp(x) for x in range(10)]', setup='import math')
tempo
Out[39]:
O código acima executa primeiro o setup import math
depois o código [math.exp(x) for x in range(10)]
, que cria uma lista com os exponencianciais de 0 a 9, 1000000 vezes.
Para sabermos qual a média do tempo de execução desse código fazemos:
In [40]:
tempo / 1000000
Out[40]:
Sabendo disso agora podemos calcular o tempo de verificação de um elemento em lista e set:
In [41]:
import timeit
import random
# PS: esse código demora para ser executado
vezes = 1000
print('tamanho | tempo da lista | tempo do set | list vs set')
tamanhos = (10 ** i for i in range(3, 8))
for tamanho in tamanhos: # cria um gerador com os valores 10^3, 10^4, ..., 10^7
setup_lista = 'l = list(range({}))'.format(tamanho)
tempo = timeit.timeit('9999999 in l', setup=setup_lista, number=vezes)
media_lista = tempo / vezes
setup_set = 's = set(range({}))'.format(tamanho)
tempo = timeit.timeit('9999999 in s', setup=setup_set, number=vezes)
media_set = tempo / vezes
msg = '{:<9}| {:<15}| {:<13}| set é {:<}x + rápido'
msg = msg.format(tamanho, round(media_lista, 8), round(media_set, 8),
round(media_lista / media_set))
print(msg)
Esse código usa alguns recursos mais avançados de formatação de string. Para entender melhor o que é feito verifique a documentação oficial do assunto.
Conjuntos também oferecem algumas operações interessantes:
A união pode ser feita a partir da função A.union(B) ou através da utilização do operador bitwise ou |
:
In [42]:
A = {2, 3, 4}
B = {3, 5, 7}
A | B
Out[42]:
In [43]:
A.union(B)
Out[43]:
A intersecção pode ser feita a partir da função A.intersection(B) ou com o operador bitwise e &
:
In [44]:
A = {2, 3, 4}
B = {3, 5, 7}
A & B
Out[44]:
In [45]:
A.intersection(B)
Out[45]:
A diferença pode ser feita a partir da função A.difference(B) ou com o operador -
:
In [46]:
A = {2, 3, 4}
B = {3, 5, 7}
A - B
Out[46]:
In [47]:
A = {2, 3, 4}
B = {3, 5, 7}
A.difference(B)
Out[47]:
A diferença pode ser feita a partir da função A.difference(B) ou com o operador ^
:
In [48]:
A = {2, 3, 4}
B = {3, 5, 7}
A ^ B
Out[48]:
In [49]:
A.symmetric_difference(B)
Out[49]:
Ok, essas funções são legais, mas eu já vi isso no ensino médio e nunca usei na minha vida, como isso vai ser útil para mim? (pelo menos foi isso que eu pensei ao ver isso)
Para testar isso vamos gerar um conjunto de nomes. Vamos usar a biblioteca externa faker que gera dados falsos.
Para usá-la é necessário instalar:
$ pip install fake-factory
Depois de instalá-la em nosso virtualenv podemos usá-la:
In [50]:
from faker import Factory
factory = Factory.create('pt_BR') # criando fábrica de dados falsos português brasileiro
nomes = {factory.name() for _ in range(10000)}
nomes
Out[50]:
Agora vamos supor que temos uma lista de nomes e queremos conferir se eles estão no conjunto de nomes. Normalmente faríamos:
In [51]:
buscas = {'João Silva', 'Ana Ferreira', 'Eduardo Santos', 'Pedro Alves', 'Enzo Correira'}
presentes = [busca for busca in buscas if busca in nomes]
presentes
Out[51]:
In [52]:
ausentes = [busca for busca in buscas if busca not in nomes]
ausentes
Out[52]:
Porém se usarmos operações de conjunto podemos fazer isso de forma mais simples e eficiente.
Para saber quais nomes buscados estão no conjunto de nomes:
In [53]:
buscas & nomes
Out[53]:
Os nomes que não estão:
In [54]:
buscas - nomes
Out[54]:
Comparando o tempo de execução das buscas usando for
contra buscas usando intersecção obtemos o seguinte resultado:
In [55]:
# tamanho | tempo set + for | tempo set + & | for vs &
# 100 | 3.945e-05 | 1.86e-06 | & é 21.25x + rápido
# 1000 | 5.844e-05 | 1.751e-05 | & é 3.34x + rápido
# 10000 | 0.00014848 | 5.991e-05 | & é 2.48x + rápido
# 100000 | 0.00015138 | 8.862e-05 | & é 1.71x + rápido
# 1000000 | 0.00014647 | 8.113e-05 | & é 1.81x + rápido
In [56]:
votos = {'joão': 10, 'maria': 25, 'ana': 40, 'pedro': 75}
votos['joão']
Out[56]:
In [57]:
votos['joão'] = 11
votos
Out[57]:
Como podem ver aqui os dicionários não mantém a ordem de seus elementos. Criamos o dict com os elementos {'joão': 10, 'maria': 25, 'ana': 40, 'pedro': 75}, porém sua ordem atual é {'ana': 40, 'joão': 11, 'maria': 25, 'pedro': 75} e, futuramente, essa ordem pode ser outra conforme alteramos esse dicionário.
In [58]:
len(votos)
Out[58]:
In [59]:
del votos['joão']
votos
Out[59]:
Vale notar que ao tentar acessar um elemento não existente é levantada uma exceção:
In [60]:
votos['joão']
As vezes pode ser necessário evitar esse tipo de comportamento. Isso pode ser feito usando a função:
In [61]:
print(votos.get('joão'))
também é possível estabelecer um valor para ser retornado caso a chave não seja encontrada:
In [62]:
votos.get('joão', 0)
Out[62]:
Por exemplo se você quiser contabilizar os votos de uma eleição em que nem todos os candidatos receberam votos e, portanto, não aparecem no dicionário votos:
In [63]:
candidatos = list(votos.keys()) + ['joão', 'muriel', 'marcola']
candidatos
Out[63]:
In [64]:
for candidato in candidatos:
print('{} recebeu {} votos.'.format(candidato, votos.get(candidato, 0)))
Podemos verificar se alguma chave existe no dicionário com o in
:
In [65]:
votos
Out[65]:
In [66]:
'ana' in votos
Out[66]:
In [67]:
'penélope' in votos
Out[67]:
In [68]:
len(votos) # número de items no dict
Out[68]:
In [69]:
outros_votos = {'milena': 100, 'mário': 1}
votos.update(outros_votos) # atualiza o dicionário votos com os items de outros_votos
votos
Out[69]:
Para acessar somente as chaves de um dicionário fazemos:
In [70]:
votos.keys()
Out[70]:
Percebe-se que o retorno não é uma lista de chaves, mas sim um dict_keys
. Não vou entrar em detalhes, mas esse dict_keys
- junto com dict_values
e dict_items
que serão mostrados mais a frente - se comportam como conjunto, por tanto verificações de pertencimento são muito eficientes, além de suportar algumas operações de conjuntos:
Para mais informações verifique a documentação oficial e o PEP 3106 em que essa mudança foi proposta
In [71]:
['maria', 'adelaide'] & votos.keys()
Out[71]:
In [72]:
['maria', 'adelaide'] - votos.keys()
Out[72]:
Para acessar somente os valores do dicionários:
In [73]:
votos.values()
Out[73]:
Como os valores não são únicos o dict_values
não pode se comportar como conjunto, por esse motivo ele não possui as operações de conjuntos
In [74]:
votos.items()
Out[74]:
Porém o dict.items()
implementa as operações de conjuntos, pois a dupla chave
e valor
são únicas:
In [75]:
[('jean', 50), ('maria', 25)] & votos.items()
Out[75]:
In [76]:
[('jean', 50), ('maria', 25)] - votos.items()
Out[76]:
Revendo iteração de dicionários:
In [77]:
for nome in votos.keys():
print(nome)
In [78]:
for qtd_votos in votos.values():
print(qtd_votos)
In [79]:
for nome, qtd_votos in votos.items(): # atribuição múltipla, lembra?
print('{} recebeu {} votos.'.format(nome.capitalize(), qtd_votos))
In [5]:
# não mude esse código, ele que gera os votos para você testar seu programa
from faker import Factory
import random
factory = Factory.create('pt_BR')
# usa distribuição de gauss para gerar quantidade de votos
votos = {factory.name(): abs(round(random.gauss(0, .2) * 10000)) for _ in range(333)}
# deixa nomes completos com somente dois nomes
votos = {nome: votos for nome, votos in votos.items() if len(nome.split()) == 2}
In [ ]:
def media(votos):
...
Assim como listas e sets, dicionários também possuem uma maneira de criar dicts com facilidade: dict comprehension
In [82]:
from faker import Factory
factory = Factory.create('pt_BR')
cpfs = {factory.name(): factory.cpf() for _ in range(10)}
cpfs
Out[82]:
In [83]:
{numero: numero ** 2 for numero in range(10)}
Out[83]:
Acontece que dict comprehension nos dá uma maneira muito bonilinda de inverter as chaves e valores de dicionários:
In [84]:
telefones = {'joão': '9941', 'ana': '9103', 'maria': '9301', 'pedro': '9203'}
telefones
Out[84]:
Normalmente faríamos:
In [85]:
nomes = {}
for nome, telefone in telefones.items():
nomes[telefone] = nome
nomes
Out[85]:
Mas com dict comprehension é muito mais fácil:
In [86]:
{telefone: nome for nome, telefone in telefones.items()}
Out[86]:
Ordenando um dict pelas chaves:
In [87]:
sorted(telefones.items())
Out[87]:
In [88]:
dict(sorted(telefones.items()))
Out[88]:
Para ordernar pelos valores precisamos "avisar" a função sorted()
para usar o valor como a chave. Isso é feito passando um argumento key
que recebe uma função que é usada para pegar o valor que será usado na ordenação. Nesse caso precisamos criar uma função que retorna o segundo elemento de nossa lista de tuplas chave e valor, referente a esse último.
In [89]:
def pega_segundo_elemento(tupla):
return tupla[1]
sorted(telefones.items(), key=pega_segundo_elemento)
Out[89]:
Adendo: no Python funções são objetos de primeira classe. Isso signfica que funções, assim como inteiros, strings e listas, podemos passá-las como parâmetros, atribuí-las a variáveis e outras operações. Esse assunto será abordando com maior profundidade na aula sobre funções.
Um outro jeito comum de realizar essa ordenação é usar funções anônimas:
In [90]:
pega_segundo_elemento = lambda x: x[1]
sorted(telefones.items(), key=pega_segundo_elemento)
Out[90]:
Geralmente o uso de funções anônimas é feito assim:
In [91]:
sorted(telefones.items(), key=lambda x: x[1])
Out[91]:
Porém, o jeito mais eficiente de realizar esse tipo de operação é utilizando a biblioteca operator
que implementa os operadores do python de forma eficiente:
In [92]:
from operator import itemgetter
sorted(telefones.items(), key=itemgetter(1))
Out[92]:
A função itemgetter()
da biblioteca operator
faz o mesmo que a função pega_segundo_elemento()
mas de forma muito mais rápida. Para saber mais sobre operator
veja sua documentação.
Dicionários podem ser utilizados para armazenar matrizes esparsas
In [17]:
from collections import defaultdict
def conta_palavras(frase):
contagem = {}
...
return contagem
In [16]:
# rode o código abaixo para testar a corretude de seu programa
assert conta_palavras("quod dolore dolore dolore modi sapiente quod ullam nostrum ullam") == {'ullam': 2, 'sapiente': 1, 'quod': 2, 'nostrum': 1, 'dolore': 3, 'modi': 1}
assert conta_palavras("soluta Soluta sapiente sapiente nostrum Sapiente dolore nostrum modi ullam") == {'ullam': 1, 'sapiente': 3, 'nostrum': 2, 'dolore': 1, 'soluta': 2, 'modi': 1}
assert conta_palavras("quod dolore dolore soluta sapiente sapiente dolore quod sapiente modi") == {'dolore': 3, 'quod': 2, 'soluta': 1, 'modi': 1, 'sapiente': 3}
assert conta_palavras("dolore Dolore quis quod dolore nostrum quod Nostrum sapiente soluta") == {'sapiente': 1, 'quod': 2, 'nostrum': 2, 'soluta': 1, 'dolore': 3, 'quis': 1}
assert conta_palavras("sapiente sapiente quod soluta quis ullam nostrum soluta ullam ullam") == {'ullam': 3, 'sapiente': 2, 'quod': 1, 'nostrum': 1, 'soluta': 2, 'quis': 1}
assert conta_palavras("modi Sapiente dolore Soluta sapiente quis soluta modi dolore ullam") == {'ullam': 1, 'sapiente': 2, 'quis': 1, 'dolore': 2, 'soluta': 2, 'modi': 2}
assert conta_palavras("quis quis nostrum nostrum sapiente quis nostrum quod quis dolore") == {'sapiente': 1, 'quod': 1, 'quis': 4, 'nostrum': 3, 'dolore': 1}
assert conta_palavras("nostrum sapiente quis ullam ullam quod ullam nostrum ullam soluta") == {'ullam': 4, 'sapiente': 1, 'quod': 1, 'nostrum': 2, 'soluta': 1, 'quis': 1}
assert conta_palavras("sapiente ullam quod quis dolore modi Quis quod dolore nostrum") == {'ullam': 1, 'sapiente': 1, 'quod': 2, 'quis': 2, 'nostrum': 1, 'dolore': 2, 'modi': 1}
assert conta_palavras("modi nostrum ullam Quis Soluta modi quis ullam modi ullam") == {'ullam': 3, 'soluta': 1, 'modi': 3, 'nostrum': 1, 'quis': 2}
comprime_chaves_dict()
que receba um dict e remova as vogais das chaves de um dicionário. Por exempo o dict {'foo': 10, 'bar': 100} deve ser comprimido para {'f': 10, 'br': 100}.
In [ ]:
def comprime_chaves_dict(dicionario):
...
In [29]:
assert comprime_chaves_dict({'molestias': 3950, 'tempore': 'possimus', 'rerum': 1200}) == {'tmpr': 'possimus', 'mlsts': 3950, 'rrm': 1200}
assert comprime_chaves_dict({'nam': 5300, 'minus': 3700, 'fugit': 8600}) == {'mns': 3700, 'nm': 5300, 'fgt': 8600}
assert comprime_chaves_dict({'magnam': 2850, 'quam': 2300, 'asperiores': 7750}) == {'qm': 2300, 'sprrs': 7750, 'mgnm': 2850}
assert comprime_chaves_dict({'quos': 'dignissimos', 'qui': 1700, 'repellendus': 'aut'}) == {'rpllnds': 'aut', 'q': 1700, 'qs': 'dignissimos'}
assert comprime_chaves_dict({'quaerat': 9850, 'magni': 8600, 'blanditiis': 'optio'}) == {'mgn': 8600, 'qrt': 9850, 'blndts': 'optio'}