Python intermediário - I¶
Visão geral sobre sequencias¶
A linguagem Python oferece diversas estruturas de dados caracterizadas como sequencias. As sequencias são implementadas com eficiência nos moldes da linguagem C.
Sequencias podem ser classificadas como:
Sequências contêinerizadas (container sequences): guardam referências aos objetos que elas contêm, que podem ser de qualquer tipo. Exemplos são:
list
,tuple
ecollections.deque
.Sequências rasas (flat sequences): armazenam fisicamente o valor de cada item dentro de seu próprio espaço na memória, e não como objetos distintos. Exemplos são:
str
,bytes
earray.array
.
Sequências rasas são mais compactas do que as contêinerizadas, mas são limitadas para armazenar valores primitivos como caracteres, bytes e números.
Outra forma de agrupar tipos de sequências é por mutabilidade:
Sequências mutáveis: podem ter seus elementos alterados. Exemplos:
list
,bytearray
,memoryview
.Sequências imutáveis: seus elementos são inalteráveis. Exemplos:
tuple
,str
,bytes
.
Tuplas¶
Tuplas são sequências imutáveis de comprimento fixo.
Exemplo: Crie uma tupla com 4 elementos e desempacote-a em 4 variáveis.
tup1 = -1,'a',12.23,'$'
a,b,c,d = tup1
tup2 = (-1,'a',12.23,'$')
e,f,g,h = tup2
print(tup1)
print(tup2)
(-1, 'a', 12.23, '$')
(-1, 'a', 12.23, '$')
Discussão:
tup1
é uma tupla criada pelo sequenciamento de elementos, sem parênteses.tup2
é uma tupla idêntica criada pelo sequenciamento de elementos, com parênteses.Em
a
,b
,c
,d
há um processo de desempacotamento (unpacking).Em
e
,f
,g
,h
temos um desempacotamento equivalente.Ambas as tuplas são equivalentes.
Exemplo: Crie uma tupla a partir de objetos existentes.
tuple(tup1)
(-1, 'a', 12.23, '$')
Discussão:
Este processo é também conhecido como type casting.
Exemplo: Construa tuplas aninhadas.
nums = (0,2,4,6,8),(1,3,5,7,9)
nums
((0, 2, 4, 6, 8), (1, 3, 5, 7, 9))
Exemplo: Altere elementos de uma tupla in-place.
t = tuple(['a',[1,2,3],False])
# imutabilidade!
t[2] = True
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-4-cca77f8062f1> in <module>
2
3 # imutabilidade!
----> 4 t[2] = True
TypeError: 'tuple' object does not support item assignment
# alteração in-place
t[1].append(4)
t
('a', [1, 2, 3, 4], False)
Discussão:
A operação
t[2] = True
retorna um erro porque a tupla não pode ser alterada.Por outro lado, a lista armazenada na segunda posição pode ser modificada in-place.
Exemplo: Crie tuplas por concatenação.
('a',1) + ('b',2) + ('c',3)
('a', 1, 'b', 2, 'c', 3)
('+','-')*3
('+', '-', '+', '-', '+', '-')
Exemplo: Imprima tabela de valores usando desempacotamento e iteração.
s = [(1,2,3),(4,5,6),(7,8,9)]
s
for (m,n,p) in s:
print('a={0}, b={1}, c={2}'.format(m,n,p))
a=1, b=2, c=3
a=4, b=5, c=6
a=7, b=8, c=9
Exemplo: Desempacote elementos em iteráveis de comprimento arbitrário.
O desempacotamento de uma tupla (ou sequência) em objetos iteráveis que tenham comprimento arbitrário pode ser feito por meio de uma expressão estrela (star expression).
# desempacota por star expression e ignora primeiro item
_,*restante = tup1
restante
['a', 12.23, '$']
Discussão:
Usamos 2 variáveis para desempacotar uma tupla de 4 elementos.
A variável
p
é uma lista contendo os últimos elementos detup1
.A variável
_
possui valor, mas é ignorada.
Hint
Use um underscore (_
) para representar uma variável que, devido ao desempacotamento, você considere “indesejada”.
Comentário:
Outros casos em que star expressions são úteis envolvem dados que possuem mais de um valor atribuível a um mesmo registro (p.ex.: 1 pessoa com 2 números de telefone), ou quando se quer quebrar iteráveis em comprimentos arbitrários.
Star expressions produzem listas.
Por exemplo:
_,*m4,_ = (3,4,8,12,16,10)
m4 # múltiplos de 4
[4, 8, 12, 16]
_,_,_,*m5 = (4,8,12,5,10)
m5 # múltiplos de 5
[5, 10]
*m6,_,_ = (6,12,5,10)
m6 # múltiplos de 6
[6, 12]
# 2 star expressions não são permitidas
*m6,*m5 = (6,12,5,10)
File "<ipython-input-13-5c1c52953256>", line 2
*m6,*m5 = (6,12,5,10)
^
SyntaxError: two starred expressions in assignment
Note
Ao usar uma star expression, certifique-se que o número de variáveis usadas no desempacotamento é consistente com os seus objetivos.
Métodos de tupla¶
Métodos são funções atribuídas a alguns objetos. Embora tuplas sejam imutáveis, elas possuem métodos bastante úteis. Aqui, consideraremos dois:
count
: conta quantas vezes um dado valor aparece na tupla.index
: localiza o índice de um valor dado.
Exemplo: Contando valores em tupla.
tupla = (4,3,2,1,1,2,3,4,2,2,2,3,3,3,1,3,1,2,3,4,2,1,3,4,2,2)
print(tupla.count(0))
print(tupla.count(3))
print(tupla.count(4))
0
8
4
Exemplo: Localizando posição.
tupla.index(3)
1
Comentário:
Caso haja valores repetidos na tupla, o método
index
retornará o índice da relativo à primeira aparição. Como o número 3 aparece 8 vezes na tupla e seu primeiro registro ocorre na segunda posição, o valor de retorno deindex
é 1.
Swap de variáveis¶
O modo Pythônico de realizar uma troca de variáveis (swap) é o seguinte:
x,y = 1,2
y,x = x,y
x,y
(2, 1)
Tuplas como registros¶
Tuplas servem para armazenar registros: cada item na tupla é o dado de um campo e a posição do item dá o significado.
Exemplo: Tuplas como registros.
# latitute e longitude da Torre Eiffel
eiffel_ll = (48.85844772530444, 2.2948031631859886)
# capitais do mundo
caps = [('Afeganistão','Cabul','Ásia'),
('Barbados','Bridgetown','América'),
('Chade','Jamena','África')]
# população indígena no Brasil por decênio
popind = [(1991,294131),(2000,734127),(2010,817963)]
for a,p in popind:
print(f'Em {a}, o Brasil possuía {p} habitantes indígenas.')
Em 1991, o Brasil possuía 294131 habitantes indígenas.
Em 2000, o Brasil possuía 734127 habitantes indígenas.
Em 2010, o Brasil possuía 817963 habitantes indígenas.
Comentários:
Imprimimos os valores dos registros por desempacotamento.
print('{:14} | {:14} | {:14}'.format('PAÍS','CAPITAL','CONTINENTE'))
print('-'*(14*3+2))
fmt = '{:14} | {:14} | {:14}'
for pais, cap, cont in caps:
print(fmt.format(pais,cap,cont))
PAÍS | CAPITAL | CONTINENTE
--------------------------------------------
Afeganistão | Cabul | Ásia
Barbados | Bridgetown | América
Chade | Jamena | África
Comentários:
Neste exemplo, simulamos uma tabela usando um formato-base e criamos uma linha de cabeçalho por concatenação de string.
Dicionários¶
Um dicionário (dict) é uma estrutura conhecida como tabela hash ou array associativo. É uma coleção de tamanho flexível composta de pares do tipo chave:valor. Criamos um dict
por diversas formas. A mais simples é usar chaves e pares explícitos.
d = {} # dict vazio
d
{}
Os pares chave-valor incorporam quaisquer tipos de dados.
d = {'par': [0,2,4,6,8], 'ímpar': [1,3,5,7,9], 'nome':'Meu dict', 'teste': True}
d
{'par': [0, 2, 4, 6, 8],
'ímpar': [1, 3, 5, 7, 9],
'nome': 'Meu dict',
'teste': True}
Acesso a conteúdo¶
Para acessar o conteúdo de uma chave, indexamos pelo seu nome.
d['par']
[0, 2, 4, 6, 8]
d['nome']
'Meu dict'
Exemplo: construindo soma e multiplicação especial.
# dict
op = {'X' :[1,2,3], 'delta' : 0.1}
# função
def sp(op):
s = [x + op['delta'] for x in op['X']]
p = [x * op['delta'] for x in op['X']]
return (s,p) # retorna tupla
soma, prod = sp(op) # desempacota
for i,s in enumerate(soma):
print(f'pos({i}) | Soma = {s} | Prod = {prod[i]}')
pos(0) | Soma = 1.1 | Prod = 0.1
pos(1) | Soma = 2.1 | Prod = 0.2
pos(2) | Soma = 3.1 | Prod = 0.30000000000000004
Inserção de conteúdo¶
# apensa variáveis
op[1] = 3
op['novo'] = (3,4,1)
op
{'X': [1, 2, 3], 'delta': 0.1, 1: 3, 'novo': (3, 4, 1)}
Alteração de conteúdo¶
op['novo'] = [2,1,4] # sobrescreve
op
{'X': [1, 2, 3], 'delta': 0.1, 1: 3, 'novo': [2, 1, 4]}
Deleção de conteúdo com del
e pop
¶
del op[1] # deleta chave
op
{'X': [1, 2, 3], 'delta': 0.1, 'novo': [2, 1, 4]}
novo = op.pop('novo') # retorna e simultaneamente deleta
novo
[2, 1, 4]
op
{'X': [1, 2, 3], 'delta': 0.1}
Listagem de chaves e valores¶
Usamos os métodos keys()
e values()
para listar chaves e valores.
arit = {'soma': '+', 'subtr': '-', 'mult': '*', 'div': '/'} # dict
k = list(arit.keys())
print(k)
val = list(arit.values())
print(val)
for v in range(len(arit)):
print(f'A operação \'{k[v]}\' de "arit" usa o símbolo \'{val[v]}\'.')
['soma', 'subtr', 'mult', 'div']
['+', '-', '*', '/']
A operação 'soma' de "arit" usa o símbolo '+'.
A operação 'subtr' de "arit" usa o símbolo '-'.
A operação 'mult' de "arit" usa o símbolo '*'.
A operação 'div' de "arit" usa o símbolo '/'.
Combinando dicionários¶
Usamos update
para combinar dicionários. Este método possui um resultado similar a extend
, usado em listas.
pot = {'pot': '**'}
arit.update(pot)
arit
{'soma': '+', 'subtr': '-', 'mult': '*', 'div': '/', 'pot': '**'}
Dicionários a partir de sequencias¶
Podemos criar dicionários a partir de sequencias existentes usando zip
.
arit = {'soma', 'subtr', 'mult', 'div', 'pot'}
ops = {'+', '-', '*', '/', '**'}
dict_novo = {}
for chave,valor in zip(arit,ops):
dict_novo[chave] = valor
dict_novo
{'div': '**', 'mult': '+', 'subtr': '-', 'pot': '/', 'soma': '*'}
Visto que um dict
é composto de várias tuplas de 2, podemos criar um de maneira ainda mais simples.
dict_novo = dict(zip(arit,ops)) # visto que dicts
dict_novo
{'div': '**', 'mult': '+', 'subtr': '-', 'pot': '/', 'soma': '*'}
Conjuntos¶
Um conjunto (set) é uma coleção não ordenada de elementos únicos. Eles podem ser criados através da função set
ou de uma expressão literal com um par de chaves {}
. Eles são parecidos com dicts, porém não possuem chaves (keys).
Exemplo: Crie o conjunto dos números pares positivos e menores do que 15.
pq1 = set(range(0,15,2))
pq2 = {0,2,4,6,8,10,12,14}
Discussão:
Em
pq1
, criamos o conjunto por função, usandorange
para gerar os números.Em
pq2
, criamos o conjunto literalmente, por extensão.
{1,2,2,3,3,4,4,4} # 'set' possui unicidade de elementos
{1, 2, 3, 4}
Operações com conjuntos¶
A seguir mostraremos uma série de operações com conjuntos. Considere os seguintes conjuntos.
A = {1,2,3}
B = {3,4,5}
C = {6}
União de conjuntos¶
A.union(B) # união
{1, 2, 3, 4, 5}
A | B # união com operador alternativo ('ou')
{1, 2, 3, 4, 5}
Atualização de conjuntos (união)¶
A união in-place de dois conjuntos pode ser feita com update
.
C
{6}
C.update(B) # C é atualizado com elementos de B
C
{3, 4, 5, 6}
C.union(A) # conjunto união com A
{1, 2, 3, 4, 5, 6}
C # os elementos de A não foram atualizados em C
{3, 4, 5, 6}
A atualização da união possui a seguinte forma alternativa com |=
.
C |= A # elementos de A atualizados em C
C
{1, 2, 3, 4, 5, 6}
Interseção de conjuntos¶
A.intersection(B) # interseção
{3}
A & B # interseção com operador alternativo ('e')
{3}
Atualização de conjuntos (interseção)¶
A interseção in-place de dois conjuntos pode ser feita com intersection_update
.
D = {1, 2, 3, 4}
E = {2, 3, 4, 5}
D.intersection(E) # interseção com E
{2, 3, 4}
D # D inalterado
{1, 2, 3, 4}
D.intersection_update(E)
D # D alterado
{2, 3, 4}
A atualização da interseção possui a seguinte forma alternativa com &=
.
D &= E
D
{2, 3, 4}
Diferença entre conjuntos¶
A
{1, 2, 3}
D
{2, 3, 4}
A.difference(D) # apenas elementos de A
{1}
D.difference(A) # apenas elementos de D
{4}
A - D # operador alternativo
{1}
D - A
{4}
Atualização de conjuntos (diferença)¶
A interseção in-place de dois conjuntos pode ser feita com difference_update
.
D = {1, 2, 3, 4}
E = {1, 2, 3, 5}
D
{1, 2, 3, 4}
D.difference(E)
D
{1, 2, 3, 4}
D.difference_update(E)
D
{4}
A atualização da diferença possui a seguinte forma alternativa com -=
.
D -= E
D
{4}
Adição ou remoção de elementos¶
A
{1, 2, 3}
A.add(4) # adiciona 4 a A
A
{1, 2, 3, 4}
B
{3, 4, 5}
B.remove(3) # remove 3 de B
B
{4, 5}
Reinicialização de um conjunto (vazio)¶
Podemos remover todos os elementos de um conjunto com clear
, deixando-o em um estado vazio.
A
{1, 2, 3, 4}
A.clear()
A # A é vazio
set()
len(A) # 0 elementos
0
Continência¶
Podemos verificar se um conjunto \(A\) é subconjunto de (está contido em) outro conjunto \(B\) (\(A \subseteq B\)) ou se \(B\) é um superconjunto para (contém) \(A\) (\(B \supseteq A\)) com issubset
e issuperset
.
B
{4, 5}
C
{1, 2, 3, 4, 5, 6}
B.issubset(C) # B está contido em C
True
C.issuperset(B) # C contém B
True
Subconjuntos e subconjuntos próprios¶
Podemos usar operadores de comparação entre conjuntos para verificar continência.
\(A \subseteq B\): \(A\) é subconjunto de \(B\)
\(A \subset B\): \(A\) é subconjunto próprio de \(B\) (\(A\) possui elementos que não estão em \(B\))
{1,2,3} <= {1,2,3} # subconjunto
True
{1,2} < {1,2,3} # subconjunto próprio
True
{1,2,3} > {1,2}
True
{1,2} >= {1,2,3}
False
Disjunção¶
Dois conjuntos são disjuntos se sua interseção é vazia. Podemos verificar a disjunção com isdisjoint
E
{1, 2, 3, 5}
G = {1,2,3,4}
G
{1, 2, 3, 4}
E.isdisjoint(G) # 1,2,3 são comuns
False
Igualdade entre conjuntos¶
Dois conjuntos são iguais se contém os mesmos elementos.
H = {3,'a', 2}
I = {'a',2, 3}
J = {1,'a'}
H == I
True
H == J
False
{1,2,2,3} == {3,3,3,2,1} # lembre-se da unicidade
True
Compreensões de lista¶
As listas são as sequências mais comuns em Python. Listas são objetos mutáveis de comprimento variável. Dominar este tipo de dado é fundamental para compreender o preenchimento de sequências de praticamente qualquer outro tipo. Começaremos aprendendo sobre compreensões de lista, ou, no jargão do Python, listcomps.
Exemplo: Construa uma lista dos códigos Unicode correspondentes dos caracteres de uma string.
palavra = '!@#$%'
cods = []
for c in palavra:
cods.append(ord(c))
cods
[33, 64, 35, 36, 37]
Discussão:
ord
produz o número ordinal correspondente de um caracter segundo o padrão Unicode. Para saber o código hexadecimal correspondente, pode-se fazerhex(ord(c))
.
Hint
Caracteres, emojis e símbolos são números codificados! Para saber mais sobre o padrão Unicode, veja este site.
Exemplo: Construa uma listcomp correspondente.
palavra = '!@#$%'
cods = [ord(c) for c in palavra]
cods
[33, 64, 35, 36, 37]
Comentário:
Uma listcomp é mais legível e sua intenção é explícita. Todavia, é preciso ser cauteloso para não criarmos códigos ilegíveis abusando de listcomps.
Como quebras de linha são ignoradas dentro de pares
[]
,()
ou{}
, é possível quebrar uma instrução de listcomp sem usar o caracter de escape para quebra de linha\
.
# listcomp com quebra de linha sem `\`
[ord(c)
for c
in palavra]
[33, 64, 35, 36, 37]
Exemplo: Construa um produto cartesiano de pares \((a,b)\), onde \(a\) é um tipo de sangue de acordo com o sistema ABO e \(b\) é o Fator Rh.
A = ['A','B','O','AB']
Rh = ['+','-']
sangue = [(a,b) for a in A for b in Rh]
sangue
[('A', '+'),
('A', '-'),
('B', '+'),
('B', '-'),
('O', '+'),
('O', '-'),
('AB', '+'),
('AB', '-')]
Discussão:
A listcomp anterior usa dois
for
que iteram sobre as sequênciasA
eRh
e constroi uma lista de tuplas do tipo (a,b).
Exemplo: Construa um produto cartesiano de pares \((a,b)\), onde \(a\) é o FatorRh e \(b\) é um tipo de sangue de acordo com o sistema ABO.
[(b,a) for a in A for b in Rh]
[('+', 'A'),
('-', 'A'),
('+', 'B'),
('-', 'B'),
('+', 'O'),
('-', 'O'),
('+', 'AB'),
('-', 'AB')]
Comentário:
Note que, neste caso, basta alterarmos a ordem dos elementos da tupla.
Exemplo: Use listcomps com condicionais para criar filtros.
# equipes da fórmula 1
f1 = ['Red Bull Racing','Mercedes',
'McLaren','Ferrari','AlphaTauri',
'Aston Martin','Alpine','Alfa Romeo Racing',
'Williams','Haas F1 Team']
# equipes que começam com A, em maiúsculas
[team.upper() for team in f1 if team[0] == 'A']
['ALPHATAURI', 'ASTON MARTIN', 'ALPINE', 'ALFA ROMEO RACING']
# equipes cujo nome não inicia com A e tem menos do que 10 caracteres
[team for team in f1
if team[0] != 'A' and len(team) < 10]
['Mercedes', 'McLaren', 'Ferrari', 'Williams']
Compreensões de conjuntos¶
Semelhantemente a listcomps, podemos realizar “setcomps”.
# comprimentos de nomes de equipes (únicos)
{len(team) for team in f1}
{6, 7, 8, 10, 12, 15, 17}
[len(team) for team in f1]
[15, 8, 7, 7, 10, 12, 6, 17, 8, 12]
Comentários:
Note que a listcomp para este caso possuiria elementos repetidos.
# comprimento de nomes de equipes que terminam com "ing"
{len(team) for team in f1 if team.endswith('ing')}
{15, 17}
Compreensões de dicionários¶
Compreensões de dicionários, ou “dictcomps”, possuem resultados similares às anteriores e são convenientes para criar dicts.
{k:v for k,v in enumerate(f1)}
{0: 'Red Bull Racing',
1: 'Mercedes',
2: 'McLaren',
3: 'Ferrari',
4: 'AlphaTauri',
5: 'Aston Martin',
6: 'Alpine',
7: 'Alfa Romeo Racing',
8: 'Williams',
9: 'Haas F1 Team'}
Comentário:
enumerate
é um objeto que constroi uma enumeração para um iterável.
# enumera a partir do índice 7, mas percorre toda a lista
{k:v for k,v in enumerate(f1,7)}
{7: 'Red Bull Racing',
8: 'Mercedes',
9: 'McLaren',
10: 'Ferrari',
11: 'AlphaTauri',
12: 'Aston Martin',
13: 'Alpine',
14: 'Alfa Romeo Racing',
15: 'Williams',
16: 'Haas F1 Team'}
Sorted¶
Listas em Python possuem um método chamado sort
com o qual podemos realizar uma ordenação in-place de uma lista. Por outro lado, a função sorted
permite que façamos o mesmo criando uma nova lista e, dessa maneira, não alterando a lista original.
# copia lista f1
f1c = f1.copy()
f1c
['Red Bull Racing',
'Mercedes',
'McLaren',
'Ferrari',
'AlphaTauri',
'Aston Martin',
'Alpine',
'Alfa Romeo Racing',
'Williams',
'Haas F1 Team']
# lista ordenada
f1c.sort()
f1c
['Alfa Romeo Racing',
'AlphaTauri',
'Alpine',
'Aston Martin',
'Ferrari',
'Haas F1 Team',
'McLaren',
'Mercedes',
'Red Bull Racing',
'Williams']
# lista f1 ordenada
f1c2 = sorted(f1)
f1c2
['Alfa Romeo Racing',
'AlphaTauri',
'Alpine',
'Aston Martin',
'Ferrari',
'Haas F1 Team',
'McLaren',
'Mercedes',
'Red Bull Racing',
'Williams']
# lista original intacta
f1
['Red Bull Racing',
'Mercedes',
'McLaren',
'Ferrari',
'AlphaTauri',
'Aston Martin',
'Alpine',
'Alfa Romeo Racing',
'Williams',
'Haas F1 Team']
Para realizar a ordenação reversa, usamos reverse=True
na função sorted
.
# ordenação re
sorted(f1,reverse=True)
['Williams',
'Red Bull Racing',
'Mercedes',
'McLaren',
'Haas F1 Team',
'Ferrari',
'Aston Martin',
'Alpine',
'AlphaTauri',
'Alfa Romeo Racing']
Aleatoriedade¶
Números aleatórios, ou também chamados de randômicos, são úteis para fabricar experimentos estatísticos, realizar amostragem e desenvolver uma série de estudos envolvendo probabilidade.
Em Python, podemos usar o módulo random
para atingir uma diversidade de propósitos em ciência de dados. Vamos explorar algumas funções para geração aleatória: choice
, random
, randint
, shuffle
e sample
.
import random
Exemplo: Crie um experimento de lançamento de dados e realize uma escolha aleatória em \(n\) lançamentos.
dado = list(range(1,7))
n = 4
for _ in range(n):
print(random.choice(dado))
4
1
5
2
Comentários:
O loop será repetido \(n\) vezes.
O método
choice
escolhe um valor ao acaso entre os disponíveis na lista.
Exemplo: Crie um experimento de probabilidade aleatório que determine a salinidade presente em 1 litro de água marinha (3 - 5%).
na = 5
saly = []
for _ in range(na):
saly.append(0.03 + (0.05-0.03)*random.random())
saly
[0.045050420686575235,
0.03604191301033581,
0.043942882418974434,
0.04297061363142192,
0.035321529319922725]
Comentários:
Este experimento equivale a coletar 5 amostras de água marinha com salinidade variável entre 3 e 5%.
O método
random
determina um número aleatório no intervalo [0,1).
Exemplo: Crie um experimento que ordena 15 arremessadores para cestas de 3 pontos durante um treinamento do time do Los Angeles Lakers (0 a 99) excluindo a lista de números reservados. Considere que se o número sorteado não estiver atribuído a nenhum jogador atual ou se já tiver sido sorteado, o técnico escolherá o próximo da lista.
Lista de números reservados: 8,13,17,19,22,24,25,32,33,34,42,44,52,99.
reservados = [8,13,17,19,22,24,25,32,33,34,42,44,52,99]
shooter = []
while len(shooter) != 15:
x = random.randint(0,99)
if x not in reservados and x not in shooter:
shooter.append(x)
print('Los Angeles Lakers :: 3-point shooters (Training Schedule):')
for s in shooter:
print(s)
Los Angeles Lakers :: 3-point shooters (Training Schedule):
40
71
23
97
3
93
68
74
27
0
39
47
30
96
88
Comentários:
O loop while encerrará quando uma lista com 15 arremessadores estiver completa.
A estrutura condicional previne que números já sorteados entrem na lista.
O método
randint
escolhe um número inteiro aleatório entre 0 e 99.
Exemplo: Crie uma alteração no schedule de treinamento anterior a partir da última lista disponível.
print(random.shuffle(shooter))
shooter
None
[30, 93, 74, 0, 23, 47, 96, 39, 68, 40, 27, 97, 71, 3, 88]
Comentário:
O método
shuffle
reordena a listashooter
randomicamente e produz um valorNone
como saída. Dessa forma, geramos uma nova lista com os mesmos arremessadores pré-selecionados, mas em ordem distinta.
Exemplo: Construa uma tabela aleatória de 3 jogos de beisebol entre times da MLB.
# lê arquivo com nome de times da MLB
with open('../database/mlb-teams.csv') as f:
mlb = f.read().splitlines()
for _ in range(3):
a,b = random.sample(mlb,2)
print(f'Game {_+1}: {a} x {b}')
Game 1: Pittsburgh Pirates x Baltimore Orioles
Game 2: San Diego Padres x Chicago White Sox
Game 3: Kansas City Royals x New York Mets
Comentários:
O método
sample
realiza uma amostragem de \(k\) valores disponíveis na lista. Aqui, escolhemos \(k=2\), de modo que 2 times são escolhidos ao acaso para comporem 3 jogos.
Expressões regulares¶
Expressões regulares (regular expressions), também conhecidas como RE, regexes, ou regex pattern são regras estabelecidas para localizar padrões desejados em strings. REs, na verdade, formam uma pequena linguagem de programação dentro de uma linguagem maior.
Em Python, REs funcionam com base em códigos escritos na linguagem C. Nossa intenção aqui é fazer uma introdução sobre a utilidade e aplicabilidade de expressões regulares. Para entender este tema com profundidade, é necessário estudar aspectos internos de compiladores.
Para que usamos regex?¶
Expressões regulares são implicitamente usadas por nós quase que diariamente quando utilizamos comandos do tipo Localizar para buscar uma palavra em uma página da internet ou dentro de um arquivo PDF. Porém, elas nos permitem fazer muito mais do que uma simples busca por palavras.
O texto a seguir é um trecho do livro Memórias Póstumas de Brás Cubas, de Machado de Assis, que pode ser recuperado pelo arquivo bras-cubas.txt:
Não durou muito a evocação; a realidade dominou logo; o presente expeliu o passado. Talvez eu exponha ao leitor, em algum canto deste livro, a minha teoria das edições humanas. O que por agora importa saber é que Virgília — chamava-se Virgília — entrou na alcova, firme, com a gravidade que lhe davam as roupas e os anos, e veio até o meu leito. O estranho levantou-se e saiu. Era um sujeito, que me visitava todos os dias para falar do câmbio, da colonização e da necessidade de desenvolver a viação férrea; nada mais interessante para um moribundo. Saiu; Virgília deixou-se estar de pé; durante algum tempo ficamos a olhar um para o outro, sem articular palavra. Quem diria? De dois grandes namorados, de duas paixões sem freio, nada mais havia ali, vinte anos depois; havia apenas dois corações murchos, devastados pela vida e saciados dela, não sei se em igual dose, mas enfim saciados. Virgília tinha agora a beleza da velhice, um ar austero e maternal; estava menos magra do que quando a vi, pela última vez, numa festa de São João, na Tijuca; e porque era das que resistem muito, só agora começavam os cabelos escuros a intercalar-se com alguns fios de prata.
Suponha que você quisesse localizar quantas vezes a palavra “Vírgilia” aparece neste trecho do livro. Com a ajuda de um editor de textos munido de um comando de busca, facilmente encontraríamos a resposta. São 4 vezes.
Agora, suponha que você quisesse localizar se, neste mesmo texto, há alguma ocorrência do seguinte padrão: palavra que se inicia com a letra “p”, maiúscula ou minúscula, possui pelo menos uma letra “i” e a letra “i” deve ser, no máximo, o quarto caracter. Claramente, não conseguiríamos cumprir esta exigência tão facilmente como encontrar “Virgília”. Com algum esforço visual, conseguimos verificar no texto que “paixões” atende este critério. Como faríamos isto usando Python? Ou melhor, como faríamos isto usando Python em um arquivo com 550.000 palavras para serem analisadas?
O padrão que acabamos de descrever pode ser procurado por meio de uma regex. Ou seja, regex são úteis quando desejamos buscar um determinado padrão em strings. A seguir, vamos usar o pequeno texto de Machado de Assis como base para aprender um pouco sobre regex.
Padrões simples, caracterese ordinários e metacaracteres¶
A tarefa mais simples que podemos executar com REs é combinar caracteres. Por exemplo, sabemos que a string 'murchos'
equivale à string 'murchos'
, mas não equivale à string 'moribundos'
. No primeiro caso, todos os caracteres se combinam e estão posicionados nos mesmos lugares; no segundo caso, embora alguns caracteres se combinem, a exemplo de 'm'
, 'r'
, 'o'
e 's'
, e estejam posicionados aparentemente em mesmos lugares (no início, terceira posição e final da string), obviamente uma string não combina exatamente com a outra.
Há muitos caracteres que podem ser usados para combinações simples. Por exemplo, quando em case insensitive, 'mur'
é um padrão encontrado tanto em 'Murcho'
, quanto em 'muralha'
ou em 'HAMURABI'
. Entretanto, há caracteres que não podem ser combinados da forma como o lemos. Por exemplo, para buscarmos o padrão '\textsuperscript'
em uma string, o caracter '\'
não pode ser usado de forma literal. Ele é um metacaracter.
Caracteres ordinários são basicamente todos os caracteres ASCII alfanuméricos, tais como 'b'
, 'B'
e '1'
.
Note
ASCII, de American Standard Code for Information Interchange, é um padrão de codificação de caracteres que foi predominantemente usado desde a década de 1960 e tornou-se um padrão da internet em 2015 contendo apenas 128 caracteres. Em 2007, ele foi suplantado pelo padrão UTF-8 (Unicode Transformation Format - 8 bit), gerido pela iniciativa Unicode (Universal Coded Character Set). O UTF-8 é um padrão mais novo que comporta 1.112.064 caracteres e também é o usual no Python 3. Os 128 primeiros caracteres do padrão UTF-8 são os 128 primeiros da lista do UTF-8, dos quais alguns tornaram-se obsoletos.
Caracteres especiais são todos os metacaracteres. A lista dos metacaracteres é a seguinte:
. ^ $ * + ? { } [ ] \ | ( )
Todos eles exercem papéis especiais na linguagem das REs como veremos a seguir.
Para efeito de curiosidade, o seguinte código imprime os caracteres ASCII mais comuns. A partir do fatiamento da lista, podemos gerar as sublistas que contêm apenas números e letras. Deixaremos isto como exercício. Cabe enfatizar, entretanto, que em Python 3, o padrão de caracter
# imprime caracteres ASCII mais comuns
# end = ' ' altera a terminação padrão '\n'
for i in range(33,127):
print(chr(i), end = ' ')
! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~
O módulo re
¶
Em Python, temos à disposição o módulo re
para trabalhar com REs. Vejamos o que ele pode nos proporcionar. Antes de prosseguir, vamos ler o arquivo e salvar o texto em uma string.
with open('../database/bras-cubas.txt', 'r') as f:
cubas = f.read()
cubas
'Não durou muito a evocação; a realidade dominou logo; o presente expeliu o passado. Talvez eu exponha ao leitor, em algum canto deste livro, a minha teoria das edições humanas. O que por agora importa saber é que Virgília — chamava-se Virgília — entrou na alcova, firme, com a gravidade que lhe davam as roupas e os anos, e veio até o meu leito. O estranho levantou-se e saiu. Era um sujeito, que me visitava todos os dias para falar do câmbio, da colonização e da necessidade de desenvolver a viação férrea; nada mais interessante para um moribundo. Saiu; Virgília deixou-se estar de pé; durante algum tempo ficamos a olhar um para o outro, sem articular palavra. Quem diria? De dois grandes namorados, de duas paixões sem freio, nada mais havia ali, vinte anos depois; havia apenas dois corações murchos, devastados pela vida e saciados dela, não sei se em igual dose, mas enfim saciados. Virgília tinha agora a beleza da velhice, um ar austero e maternal; estava menos magra do que quando a vi, pela última vez, numa festa de São João, na Tijuca; e porque era das que resistem muito, só agora começavam os cabelos escuros a intercalar-se com alguns fios de prata.'
# importa o módulo
import re
As principais funções do módulo re
podem ser resumidas ao seguinte modelo:
re.f(pattern,string)
onde f é o nome da função, pattern é o padrão, isto é, a regex, e string é a string na qual o padrão é procurado. Exploraremos algumas delas a seguir compreendendo, simultaneamente, o papel desempenhado pelos metacaracteres.
Exemplo: Procure pelo padrão “que” no texto-base e conte quantas vezes ele aparece.
# busca todas as combinações
que = re.findall('que',cubas)
print(que)
print(len(que))
['que', 'que', 'que', 'que', 'que', 'que', 'que']
7
Discussão:
O método
findall
procura emcubas
todas as combinações do padrão “que”len
permite que contemos o número de elementos da lista e, consequentemente, o número de vezes que o padrão aparece. Entretanto, observemos que o padrão poderia ser algo que não formasse uma palavra inteligível. Vejamos o próximo exemplo:
# padrão presente, mas não é palavra inteligível
re.findall('xou',cubas)
['xou']
Buscas por caracteres ordinários são sempre exatas, no sentido de que o caracter é combinado como ele é. Vejamos um exemplo que conta as vogais que aparecem no texto.
for vogal in ['a','e','i','o','u']:
print(f'A vogal \'{vogal}\' foi \
localizada {len(re.findall(vogal,cubas))} vezes no texto.')
A vogal 'a' foi localizada 143 vezes no texto.
A vogal 'e' foi localizada 104 vezes no texto.
A vogal 'i' foi localizada 65 vezes no texto.
A vogal 'o' foi localizada 83 vezes no texto.
A vogal 'u' foi localizada 43 vezes no texto.
Exemplo: Busque pelo padrão “Tijuca” no texto e determine suas posições de início e término.
# busca por combinação retornado posição
p = re.search('Tijuca',cubas)
p.start(), p.end(), p.group()
(1042, 1048, 'Tijuca')
Discussão:
O método
search
procura pelo padrão'Tijuca'
emcubas
e retorna três informações:start
: a posição inicial do padrãoend
: a posição final do padrãogroup
: o padrão localizado
O padrão aparece apenas uma vez no texto na fatia
cubas[1042:1048]
.
Comentários:
Caso houvesse repetição do padrão, apenas os índices da primeira aparição seriam retornados. Vejamos:
p2 = re.search('que',cubas)
p2.start(), p2.end(), p2.group()
(179, 182, 'que')
Vimos anteriormente que há 7 aparições do padrão “que” no texto-base. Porém, apenas a primeira é retornada por search
. Vejamos:
# uma maneira de mostrar que há apenas
# um 'que' até o caracter 182 de 'cubas'
# por meio de split e interseção de conjuntos
set(cubas[0:182].split(' ')).intersection({'que'})
{'que'}
Como este conjunto é unitário, há apenas um padrão ‘que’ até a posição 182 de cubas
.
Exemplo: Busque pelo padrão “Não” no texto e verifique se ele aparece no início da string.
p3 = re.match('Não',cubas)
p3.start()
0
Discussão:
O método
match
busca por um determinado padrão que aparece somente no início de uma string. Quando tal combinação de caracteres é encontrada o método retorna um objeto. Caso contrário, retornaNoneType
.No exemplo anterior, o método
start
mostra que o primeiro caracter do padrão'Não'
, qual seja,'N'
, ocupa a primeira posição da string, isto é, a posição 0.
Comentários:
Enquanto
re.search
realiza a busca por um padrão em qualquer posição da string,re.match
busca se esse mesmo padrão ocorre, exclusivamente, no início da string.Há muitas situações em que
match
será a escolha a fazer em seu programa. Em uma string de várias linhas, por exemplo,match
procurará pelo padrão apenas na primeira linha e ignorará a ocorrência dele nas demais. Opostamente,search
procurará por ele em todas as linhas. Se forem detectadas mais de uma ocorrência, retornará a posição da primeira ocorrência.Em ambos os métodos, quando o padrão não existe na string, o objeto retornado é um
NoneType
.
Exemplo: Substitua o padrão “Virgília” por “Ofélia” em toda a string do texto.
cubas_novo = re.sub('Virgília','Ofélia',cubas)
cubas_novo
'Não durou muito a evocação; a realidade dominou logo; o presente expeliu o passado. Talvez eu exponha ao leitor, em algum canto deste livro, a minha teoria das edições humanas. O que por agora importa saber é que Ofélia — chamava-se Ofélia — entrou na alcova, firme, com a gravidade que lhe davam as roupas e os anos, e veio até o meu leito. O estranho levantou-se e saiu. Era um sujeito, que me visitava todos os dias para falar do câmbio, da colonização e da necessidade de desenvolver a viação férrea; nada mais interessante para um moribundo. Saiu; Ofélia deixou-se estar de pé; durante algum tempo ficamos a olhar um para o outro, sem articular palavra. Quem diria? De dois grandes namorados, de duas paixões sem freio, nada mais havia ali, vinte anos depois; havia apenas dois corações murchos, devastados pela vida e saciados dela, não sei se em igual dose, mas enfim saciados. Ofélia tinha agora a beleza da velhice, um ar austero e maternal; estava menos magra do que quando a vi, pela última vez, numa festa de São João, na Tijuca; e porque era das que resistem muito, só agora começavam os cabelos escuros a intercalar-se com alguns fios de prata.'
Discussão:
O método
sub
realiza a substituição de um padrão por outro na string passada e sua sintaxe mais genérica é do tipore.sub(p,r,s)
, ondep
é o padrão que se deseja substituir porr
na strings
.No exemplo dado, a operação com
sub
simplesmente substituirá todas as entradas da palavra “Vírgília” por “Ofélia” emcubas
, resultando em uma string nova chamadacubas_novo
.
Exemplo: Substitua o padrão “Virgília” por “Ofélia” apenas nas duas primeiras aparições
cubas_novo2 = re.sub('Virgília','Ofélia',cubas,count=2)
cubas_novo2
'Não durou muito a evocação; a realidade dominou logo; o presente expeliu o passado. Talvez eu exponha ao leitor, em algum canto deste livro, a minha teoria das edições humanas. O que por agora importa saber é que Ofélia — chamava-se Ofélia — entrou na alcova, firme, com a gravidade que lhe davam as roupas e os anos, e veio até o meu leito. O estranho levantou-se e saiu. Era um sujeito, que me visitava todos os dias para falar do câmbio, da colonização e da necessidade de desenvolver a viação férrea; nada mais interessante para um moribundo. Saiu; Virgília deixou-se estar de pé; durante algum tempo ficamos a olhar um para o outro, sem articular palavra. Quem diria? De dois grandes namorados, de duas paixões sem freio, nada mais havia ali, vinte anos depois; havia apenas dois corações murchos, devastados pela vida e saciados dela, não sei se em igual dose, mas enfim saciados. Virgília tinha agora a beleza da velhice, um ar austero e maternal; estava menos magra do que quando a vi, pela última vez, numa festa de São João, na Tijuca; e porque era das que resistem muito, só agora começavam os cabelos escuros a intercalar-se com alguns fios de prata.'
Discussão:
É possível usar a keyword
count
e especificar quantas vezes, em ordem, o padrão deverá ser substituído.Como
count=2
, o métodosub
fará a substituição de “Virgília” por “Ofélia” apenas nas duas primeiras ocorrências do padrão, das quatro existentes no texto.
Comentários:
Note que se usássemos
re.findall
para fazer uma breve verificação nas stringscubas
,cubas_novo
ecubas_novo2
, o número de ocorrências de “Virgília” diferiria em cada caso.
# original
print(len(re.findall('Virgília',cubas)))
# "Virgília" -> "Ofélia" integralmente
print(len(re.findall('Virgília',cubas_novo)))
# "Virgília" -> "Ofélia" em 2 ocorrências apenas
print(len(re.findall('Virgília',cubas_novo2)))
4
0
2
Exemplo: Seccione o texto em várias substrings usando a vírgula (“,”) como ponto de quebra:
cubas_sep = re.split(',',cubas)
cubas_sep
['Não durou muito a evocação; a realidade dominou logo; o presente expeliu o passado. Talvez eu exponha ao leitor',
' em algum canto deste livro',
' a minha teoria das edições humanas. O que por agora importa saber é que Virgília — chamava-se Virgília — entrou na alcova',
' firme',
' com a gravidade que lhe davam as roupas e os anos',
' e veio até o meu leito. O estranho levantou-se e saiu. Era um sujeito',
' que me visitava todos os dias para falar do câmbio',
' da colonização e da necessidade de desenvolver a viação férrea; nada mais interessante para um moribundo. Saiu; Virgília deixou-se estar de pé; durante algum tempo ficamos a olhar um para o outro',
' sem articular palavra. Quem diria? De dois grandes namorados',
' de duas paixões sem freio',
' nada mais havia ali',
' vinte anos depois; havia apenas dois corações murchos',
' devastados pela vida e saciados dela',
' não sei se em igual dose',
' mas enfim saciados. Virgília tinha agora a beleza da velhice',
' um ar austero e maternal; estava menos magra do que quando a vi',
' pela última vez',
' numa festa de São João',
' na Tijuca; e porque era das que resistem muito',
' só agora começavam os cabelos escuros a intercalar-se com alguns fios de prata.']
Na próxima sessão, vamos incorporar sintaxes especiais nos métodos que aprendemos para realizar propósitos bem mais avançados.
Discussão:
O método
split
quebra a string em partes (substrings) substituindo usando o padrão passado ao método como ponto de quebra. Neste exemplo, as vírgulas são removidas e uma lista de strings é retornada.Ao utilizar
re.split(a,b)
, lembre-se quea
é um padrão a ser encontrado na stringb
. Ele não é necessariamente um caracter único, mas uma expressão regular.
Exemplo: Separe a string do texto em no máximo 3 substrings usando o padrão “ão” como ponto de quebra.
# maxsplit = n; logo, n + 1 substrings
cubas_sep3 = re.split("ão",cubas,maxsplit=2)
cubas_sep3
['N',
' durou muito a evocaç',
'; a realidade dominou logo; o presente expeliu o passado. Talvez eu exponha ao leitor, em algum canto deste livro, a minha teoria das edições humanas. O que por agora importa saber é que Virgília — chamava-se Virgília — entrou na alcova, firme, com a gravidade que lhe davam as roupas e os anos, e veio até o meu leito. O estranho levantou-se e saiu. Era um sujeito, que me visitava todos os dias para falar do câmbio, da colonização e da necessidade de desenvolver a viação férrea; nada mais interessante para um moribundo. Saiu; Virgília deixou-se estar de pé; durante algum tempo ficamos a olhar um para o outro, sem articular palavra. Quem diria? De dois grandes namorados, de duas paixões sem freio, nada mais havia ali, vinte anos depois; havia apenas dois corações murchos, devastados pela vida e saciados dela, não sei se em igual dose, mas enfim saciados. Virgília tinha agora a beleza da velhice, um ar austero e maternal; estava menos magra do que quando a vi, pela última vez, numa festa de São João, na Tijuca; e porque era das que resistem muito, só agora começavam os cabelos escuros a intercalar-se com alguns fios de prata.']
Discussão:
A keyword
maxsplit
permite que limitemos o número de quebras. Neste exemplo, comomaxsplit=2
, há dois pontos de quebra e, portanto, 3 substrings.Note que, neste exemplo, a substring 1 é formada por apenas um caracter, ao passo que a substring 3 comporta todos os caracteres além da segunda ocorrência do padrão “ão”.
Perceba, adicionalmente, que o padrão não é preservado durante o split.
Sintaxes especiais¶
Os metacaracteres podem ser usados para criar REs das formas mais variadas (e criativas) possíveis. Vejamos exemplos.
Metacaracter .
¶
Serve para combinar qualquer caracter, exceto o newline (\n
), se usado no modo padrão. Em uso não padrão, pode também combinar-se com \n
.
Exemplo: Localize padrões com 5 caracteres contendo “lh” na 3a. e 4a. posições.
re.findall('..lh.',cubas)
['e lhe', ' olha', 'velhi']
Comentário:
Espaço também é um caracter. Por isso, na lista, temos também o padrão possível
'e lhe'
.
Exemplo: Localize padrões iniciando com a letra T maiúscula e contendo, ao todo, 3 caracteres.
re.findall('T...',cubas)
['Talv', 'Tiju']
Metacaracter \
¶
Serve para criar escapes de caracteres especiais ou sinalizar uma sequência especial.
Exemplo: Quebre a string usando o ponto final (“.”) como ponto de quebra.
# escape do metacaracter '.'
mt1 = re.split('\.',cubas)
mt1
['Não durou muito a evocação; a realidade dominou logo; o presente expeliu o passado',
' Talvez eu exponha ao leitor, em algum canto deste livro, a minha teoria das edições humanas',
' O que por agora importa saber é que Virgília — chamava-se Virgília — entrou na alcova, firme, com a gravidade que lhe davam as roupas e os anos, e veio até o meu leito',
' O estranho levantou-se e saiu',
' Era um sujeito, que me visitava todos os dias para falar do câmbio, da colonização e da necessidade de desenvolver a viação férrea; nada mais interessante para um moribundo',
' Saiu; Virgília deixou-se estar de pé; durante algum tempo ficamos a olhar um para o outro, sem articular palavra',
' Quem diria? De dois grandes namorados, de duas paixões sem freio, nada mais havia ali, vinte anos depois; havia apenas dois corações murchos, devastados pela vida e saciados dela, não sei se em igual dose, mas enfim saciados',
' Virgília tinha agora a beleza da velhice, um ar austero e maternal; estava menos magra do que quando a vi, pela última vez, numa festa de São João, na Tijuca; e porque era das que resistem muito, só agora começavam os cabelos escuros a intercalar-se com alguns fios de prata',
'']
Comentário:
Note que, todas as substrings intermediárias passaram a iniciar-se com um caracter de espaço. Como isto é pouco útil para nós, poderíamos ter utilizado o padrão
.
(com espaço) para realizar a quebra.
# considera um espaço no padrão
mt2 = re.split('\. ',cubas)
mt2
['Não durou muito a evocação; a realidade dominou logo; o presente expeliu o passado',
'Talvez eu exponha ao leitor, em algum canto deste livro, a minha teoria das edições humanas',
'O que por agora importa saber é que Virgília — chamava-se Virgília — entrou na alcova, firme, com a gravidade que lhe davam as roupas e os anos, e veio até o meu leito',
'O estranho levantou-se e saiu',
'Era um sujeito, que me visitava todos os dias para falar do câmbio, da colonização e da necessidade de desenvolver a viação férrea; nada mais interessante para um moribundo',
'Saiu; Virgília deixou-se estar de pé; durante algum tempo ficamos a olhar um para o outro, sem articular palavra',
'Quem diria? De dois grandes namorados, de duas paixões sem freio, nada mais havia ali, vinte anos depois; havia apenas dois corações murchos, devastados pela vida e saciados dela, não sei se em igual dose, mas enfim saciados',
'Virgília tinha agora a beleza da velhice, um ar austero e maternal; estava menos magra do que quando a vi, pela última vez, numa festa de São João, na Tijuca; e porque era das que resistem muito, só agora começavam os cabelos escuros a intercalar-se com alguns fios de prata.']
Metacaracter ^
¶
Também chamado caret, serve para buscar padrões no início de uma string.
Exemplo: Use as substrings do exemplo anterior para buscar quais delas iniciam-se por 'T'
.
for k,s in enumerate(mt2):
aux = re.match('^T',s)
if aux: # se lista não for vazia
print(f'---> Padrão detectado na \
substring {k}:\n\"{s}\"')
---> Padrão detectado na substring 1:
"Talvez eu exponha ao leitor, em algum canto deste livro, a minha teoria das edições humanas"
Comentário:
Poderíamos ter usado
re.search
oure.match
também para atingir o mesmo propósito, visto que a expressão regular'^T'
força o início com “T”.
Metacaracter $
¶
Visto em sentido oposto a ^
, $
serve para buscar padrões no final de uma string, ou logo antes de um caracter newline.
Exemplo: Use as substrings do exemplo anterior para buscar quais delas terminam por 'o'
.
for k,s in enumerate(mt2):
aux = re.findall('o$',s)
if aux: # se lista não for vazia
print(f'---> Padrão detectado na \
substring {k}:\n\"{s}\"')
---> Padrão detectado na substring 0:
"Não durou muito a evocação; a realidade dominou logo; o presente expeliu o passado"
---> Padrão detectado na substring 2:
"O que por agora importa saber é que Virgília — chamava-se Virgília — entrou na alcova, firme, com a gravidade que lhe davam as roupas e os anos, e veio até o meu leito"
---> Padrão detectado na substring 4:
"Era um sujeito, que me visitava todos os dias para falar do câmbio, da colonização e da necessidade de desenvolver a viação férrea; nada mais interessante para um moribundo"
Discussão:
Neste exemplo, identificamos todas as substrings que terminam com o caracter ‘o’.
Com expressões regulares mais exatas, restringimos as possibilidades. Vejamos o exemplo a seguir.
# busca substring terminando com 'undo'
for k,s in enumerate(mt2):
aux = re.findall('undo$',s)
if aux: # se lista não for vazia
print(f'---> Padrão detectado na \
substring {k}:\n\"{s}\"')
---> Padrão detectado na substring 4:
"Era um sujeito, que me visitava todos os dias para falar do câmbio, da colonização e da necessidade de desenvolver a viação férrea; nada mais interessante para um moribundo"
Comentário:
Note que para buscar o caracter literal ‘$’ em uma string devemos usar o caracter de escape. Vejamos o próximo exemplo.
real = 'João me pagou R$ 150,00.'
v = re.search('\$',real)
print(f'Caracter $ identificado na posição {v.start()}.')
Caracter $ identificado na posição 15.
Metacaracter *
¶
Serve para buscar 0 ou mais repetições de um caracter precedente. Este caracter especial é de repetição.
Exemplo:
re.findall('hav*.',cubas)
['ha ', 'ha ', 'ham', 'har', 'havi', 'havi', 'ha ']
Discussão:
A RE
'hav*.'
procura por padrões que atendem às seguintes restrições:o caracter ‘v’ aparece 0 ou mais vezes após ‘ha’.
quando não houver ‘v’, o terceiro caracter passa a ser qualquer um que vier na sequência por causa do metacaracter ‘.’.
quando houver um ‘v’, o quarto caracter é qualquer um
quando houver n repetições de ‘v’ após ‘a’, todos os n são considerados, e o n+1-ésimo caracter é qualquer um.
Comentários:
Descrever em palavras o que a expressão regular faz exatamente não é sempre muito fácil. Vejamos mais um exemplo.
c = 'ha hahaa hav havv havva-havvva; haiiaa havia'
re.findall('hav*.',c)
['ha ', 'hah', 'hav ', 'havv ', 'havva', 'havvva', 'hai', 'havi']
Metacaracter +
¶
Serve para buscar 1 ou mais repetições de um caracter precedente. Assim como *
, +
também é um caracter especial de repetição.
Exemplo:
re.findall('hav+.',cubas)
['havi', 'havi']
Discussão:
A RE
'hav+.'
é similar à anterior. Porém, pelo fato de ser obrigatória a ocorrência de pelo menos 1 caracter ‘v’ após ‘a’, os padrões ‘ha ‘, ‘ham’ e ‘har’ são excluídos.Se encontradas n ocorrências de ‘v’, o padrão será encerrado pelo n+1-ésimo caracter arbitrário
Como no texto existe tal padrão com apenas uma ocorrência de ‘v’, o quarto caracter, sendo arbitrário, é encontrado como ‘i’.
O padrão ‘havia’, que forma uma palavra compreensível em Português, é ignorado por conter 5 caracteres.
Metacaracter ?
¶
Serve para buscar 0 ou 1 repetição de um caracter precedente. Assim como *
, +
também é um caracter especial de repetição.
Exemplo:
re.findall('s?an.',cubas)
['ant', 'ana', 'ano', 'anh', 'ant', 'sant', 'ant', 'and', 'ano', 'and']
Comentários:
Esta RE procurará por 0 ou 1 repetição de ‘s’ seguida por ‘an’ e qualquer outro caracter.
Neste caso, a substring ‘ssan’, por exemplo, existente na palavra ‘interessante’ não seria coberta por
?
.
Metacaracteres []
¶
Os colchetes devem ser usados, juntos, em par, para indicar um conjunto de caracteres sobre os quais a expressão regular deve operar. Em geral, usa-se o par []
para os seguintes propósitos:
Listar caracteres individualmente
A RE [vnd]
combinará os caracteres 'v'
, 'n'
ou 'd'
.
Exemplo:
# localiza todas as ocorrências
# de 'v', 'n', ou 'd'
print(re.findall('[vnd]',cubas))
['d', 'v', 'd', 'd', 'd', 'n', 'n', 'd', 'v', 'n', 'n', 'd', 'v', 'n', 'd', 'd', 'n', 'v', 'n', 'n', 'v', 'v', 'd', 'd', 'd', 'v', 'n', 'v', 'n', 'v', 'n', 'v', 'v', 'd', 'd', 'd', 'd', 'n', 'd', 'n', 'd', 'd', 'd', 'd', 'n', 'v', 'v', 'v', 'n', 'd', 'n', 'n', 'n', 'd', 'd', 'd', 'd', 'n', 'v', 'd', 'd', 'n', 'd', 'n', 'd', 'd', 'd', 'n', 'd', 'v', 'v', 'n', 'n', 'd', 'v', 'n', 'd', 'd', 'v', 'd', 'v', 'd', 'd', 'd', 'n', 'd', 'n', 'd', 'n', 'd', 'v', 'n', 'v', 'n', 'd', 'n', 'd', 'v', 'v', 'n', 'd', 'n', 'd', 'v', 'n', 'n', 'd']
Localizar um intervalo de caracteres
Uma RE do tipo [a-d]
combinará os caracteres de 'a'
a 'd'
, ao passo que [a-z]
combinará todas as letras minúsculas da tabela ASCII (e não Unicode!), ou seja, de 'a'
a 'z'
.
Do mesmo modo, [0-4]
combinará os dígitos de 0 a 4, e [0-9]
todos os dígitos decimais. Por outro lado, a expressão [0-3][3-8]
combinará todos os números de dois dígitos de 03 a 38.
Se o símbolo -
for escapado (p.ex. [a\-d]
), ou se for colocado como primeiro ou último caracter (p.ex. [-b]
ou [b-]
), ele significará o hífen literal (‘-’).
Exemplo:
re.findall('[a-z]um',cubas)
['gum', 'hum', 'gum', 'num']
Discussão:
Esta RE procurará todos os padrões que iniciam por qualquer letra minúscula (de ‘a’ a ‘z’) seguidas por ‘um’.
Exemplo:
re.findall('[v-z][a-].',cubas)
['va-', 'va,', 'vam', 'van', 'va ', 'zaç', 'vas', 'za ', 'va ', 'vam']
Discussão:
Esta RE procurará todos os padrões que iniciam por uma letra minúscula entre ‘v’ e ‘z’ seguidas por ‘a’, ‘-’, ou ‘a-’ e um caracter qualquer adicional.
Exemplo:
# mesmo efeito que anterior
re.findall('[v-z][-a].',cubas)
['va-', 'va,', 'vam', 'van', 'va ', 'zaç', 'vas', 'za ', 'va ', 'vam']
Localizar caracteres especiais como literais
Os caracteres especiais perdem seu status quando aparecem entre colchetes. Por exemplo, a RE [(+*)]
procurará por qualquer um dos literais (
, +
, *
ou )
.
ex = 'Quanto é (22 + 3)*1 - 6*1?'
re.findall('[0-9][)*]',ex)
['3)', '6*']
Discussão:
Esta RE busca padrões em que um dígito de 0 a 9 precede um ‘)’ ou ‘*’ literalmente.
re.findall('[0-9][?]',ex)
['1?']
Discussão:
Esta RE busca padrões em que um dígito de 0 a 9 precede um ‘?’ literal.
Metacaracteres {}
¶
As chaves, assim como os colchetes, devem ser usadas juntas, em par, para localizar cópias ou repetições de uma expressão regular. Em geral, ela é usada para os seguintes objetivos:
Localizar exatamente m cópias de uma RE anterior
Exemplo:
# busca pelo padrão 'rr'
re.search('r{2}',cubas).group()
'rr'
Exemplo:
ex2 = '0110011000110011001010110100110111111000'
re.findall('0{2}',ex2)
['00', '00', '00', '00', '00', '00']
re.findall('1{3}',ex2)
['111', '111']
# busca 000000 - 111111
# com variações de 0,1 intermediárias
re.findall('[0-1]{6}',ex2)
['011001', '100011', '001100', '101011', '010011', '011111']
Localizar entre m e n repetições de uma RE anterior
Exemplos:
set(re.findall('10{2,3}',ex2))
{'100', '1000'}
set(re.findall('01{2,3}',ex2))
{'011', '0111'}
set(re.findall('[0-1]{2,3}',ex2))
{'001', '010', '011', '100', '101', '111'}
Metacaracter |
¶
Usamos o pipe para significar “ou” com o objetivo de localizar mais de uma RE. Por exemplo, A|B
buscará padrões determinados pela RE A
ou por B
. Pipes adicionais constroem sintaxes conjuntivas. Ou seja, A|B|C|D...|Z
significaria “A ou B ou C ou … D”.
Exemplos:
# busca por 'rre' ou 'ssa'
re.findall('r{2}e|s{2}a',cubas)
['ssa', 'rre', 'ssa']
# busca por 'cha', 'cho' ou
# 'lha', 'lhi' ou
# 'pra', 'pre', 'pri', 'pro', 'pru'
re.findall('ch[ao]|lh[ai]|pr[aeiou]',cubas)
['pre', 'cha', 'lha', 'cho', 'lhi', 'pra']
Metacaracteres ()
¶
Servem para criar grupos de captura. Um grupo de captura é formado por uma RE confinada entre parênteses. Grupos de captura podem ser aplicados para extrair padrões que possuam uma certa estrutura.
Exemplo:
table = ['alfa00-LAX','beta22-PET', 'zeta92-XIR']
for t in table:
pat = re.match("([a-z][a-z][a-z][a-z][0-9][0-9])-([A-Z]{3})", t)
if pat:
print(pat.groups())
('alfa00', 'LAX')
('beta22', 'PET')
('zeta92', 'XIR')
Discussão:
A RE acima é composta de dois grupos de captura que são separados pelo
-
literal.O primeiro grupo de captura é
([a-z][a-z][a-z][a-z][0-9][0-9])
O segundo grupo de captura é
([A-Z]{3})
Tuplas com dois elementos são impressas com
groups()
.
Comentários:
Como se vê, os grupos de captura permitiram que identificássemos padrões separados em uma lista de strings que continha uma estrutura predefinida.
Entretanto, as REs anteriores criadas como grupos de captura poderiam ser definidas de uma forma muito mais concisa através de identificadores.
Identificadores e tokens gerais¶
Ao trabalhar com REs, podemos utilizar identificadores e tokens diversos para significar padrões de maneira concisa.
O exemplo anterior sobre grupos de captura, por exemplo, poderia ser escrito de outra forma:
for t in table:
pat = re.match("(\w+)\W(\w+)", t)
if pat:
print(pat.groups())
('alfa00', 'LAX')
('beta22', 'PET')
('zeta92', 'XIR')
Neste caso, \w
e \W
são chamados identificadores.
A tabela a seguir resume os principais identificadores e seu significado.
Identificador |
Significado |
---|---|
|
qualquer caracter que é um dígito decimal. Equivalente a |
|
qualquer caracter exceto dígido (não-dígito). Equivalente a |
|
qualquer caracter Unicode. Se a flag |
|
o oposto de |
|
qualquer caracter que é um espaço. |
|
qualquer caracter que não seja espaço. |
A tabela a seguir resume tokens gerais e seu significado.
Token |
Significado |
---|---|
|
quebra de linha. Newline. |
|
carriage return |
|
TAB |
|
caracter nulo |
Exemplo: Identificando padrões de data e hora.
dt = ['2021-04-06 10:32:00', '2020-12-03 01:12:58']
for m in dt:
pat = re.match('(\d+)-(\d+)-(\d+)\s(\d+):(\d+):(\d+)',m)
if pat:
print(pat.groups())
('2021', '04', '06', '10', '32', '00')
('2020', '12', '03', '01', '12', '58')
Exemplo: Identificando cores em padrão hexadecimal.
cores = ['(1,23,43)','#3ed4f4', '#ffcc00','(C,M,Y,K)','#9999ff']
for c in cores:
ok = re.match('#[a-fA-F0-9]{6}',c)
if ok:
print(ok.group())
#3ed4f4
#ffcc00
#9999ff
Exemplo: Identificando URLs no domínio Wikipedia Português.
urls = ['http://pt.wikipedia.org/wiki/Bras',
'http://wikipedia.org/wiki/Bras',
'https://pt.wikipedia.org/wiki/Bras',
'http://pt.wikipedia.org/wik/Bras',
'https://pt.wikipedia.org/wiki/Cubas']
for u in urls:
ok = re.match('((http|https)://)?(pt.wikipedia.org/wiki/).+',u)
if ok:
print(ok.group())
http://pt.wikipedia.org/wiki/Bras
https://pt.wikipedia.org/wiki/Bras
https://pt.wikipedia.org/wiki/Cubas
Compilação e literais¶
Até agora abordamos REs de uma maneira direta, mostrando como podemos localizar padrões pela utilização de alguns métodos. Vale comentar que REs podem ser compiladas como objetos mais abstratos.
Para compilar expressões, podemos utilizar o método compile
, no qual REs são manipuladas como strings.
Exemplo:
rex = re.compile('[p-t]a[b-d]a?')
rex.findall(cubas)
['sad', 'sab', 'rad', 'tad', 'sac', 'sac']
Aqui, rex
é um objeto abstrato.
print(type(rex))
<class 're.Pattern'>
Se imprimirmos rex
, veremos o seguinte:
rex
re.compile(r'[p-t]a[b-d]a?', re.UNICODE)
O prefixo r
torna a string literal e serve para tratar problemas com escape. Todavia, deixaremos esta discussão para um momento posterior. Para saber mais, leia sobre a Praga do Backspace.
Recursos de aprendizagem¶
Para aprofundar seu entendimento sobre expressões regulares, recomendamos os seguintes sites: