Fundamentos de Sequências em Python

Intermediate

This tutorial is from open-source community. Access the source code

Introdução

Sequências Python (Python Sequences) são coleções ordenadas de itens. Elas são indexadas por inteiros.

Este é um Lab Guiado, que fornece instruções passo a passo para ajudá-lo a aprender e praticar. Siga as instruções cuidadosamente para completar cada etapa e ganhar experiência prática. Dados históricos mostram que este é um laboratório de nível intermediário com uma taxa de conclusão de 80%. Recebeu uma taxa de avaliações positivas de 100% dos estudantes.

Tipos de Dados de Sequência (Sequence Datatypes)

Python possui três tipos de dados de sequência (sequence).

  • String: 'Hello'. Uma string é uma sequência de caracteres.
  • List: [1, 4, 5].
  • Tuple: ('GOOG', 100, 490.1).

Todas as sequências são ordenadas, indexadas por inteiros e possuem um comprimento.

a = 'Hello'               ## String
b = [1, 4, 5]             ## List
c = ('GOOG', 100, 490.1)  ## Tuple

## Ordem indexada
a[0]                      ## 'H'
b[-1]                     ## 5
c[1]                      ## 100

## Comprimento da sequência
len(a)                    ## 5
len(b)                    ## 3
len(c)                    ## 3

Sequências podem ser replicadas: s * n.

>>> a = 'Hello'
>>> a * 3
'HelloHelloHello'
>>> b = [1, 2, 3]
>>> b * 2
[1, 2, 3, 1, 2, 3]
>>>

Sequências do mesmo tipo podem ser concatenadas: s + t.

>>> a = (1, 2, 3)
>>> b = (4, 5)
>>> a + b
(1, 2, 3, 4, 5)
>>>
>>> c = [1, 5]
>>> a + c
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate tuple (not "list") to tuple

Fatiamento (Slicing)

Fatiamento (Slicing) significa obter uma subsequência de uma sequência. A sintaxe é s[start:end]. Onde start e end são os índices da subsequência desejada.

a = [0,1,2,3,4,5,6,7,8]

a[2:5]    ## [2,3,4]
a[-5:]    ## [4,5,6,7,8]
a[:3]     ## [0,1,2]
  • Os índices start e end devem ser inteiros.
  • Fatias (Slices) não incluem o valor final. É como um intervalo semiaberto da matemática.
  • Se os índices forem omitidos, eles assumem o padrão do início ou do fim da lista.

Reatribuição de Fatias (Slice re-assignment)

Em listas, fatias (slices) podem ser reatribuídas e excluídas.

## Reatribuição (Reassignment)
a = [0,1,2,3,4,5,6,7,8]
a[2:4] = [10,11,12]       ## [0,1,10,11,12,4,5,6,7,8]

Nota: A fatia reatribuída não precisa ter o mesmo comprimento.

## Exclusão (Deletion)
a = [0,1,2,3,4,5,6,7,8]
del a[2:4]                ## [0,1,4,5,6,7,8]

Reduções de Sequência (Sequence Reductions)

Existem algumas funções comuns para reduzir uma sequência a um único valor.

>>> s = [1, 2, 3, 4]
>>> sum(s)
10
>>> min(s)
1
>>> max(s)
4
>>> t = ['Hello', 'World']
>>> max(t)
'World'
>>>

Iteração sobre uma sequência (Iteration over a sequence)

O laço for (for-loop) itera sobre os elementos em uma sequência.

>>> s = [1, 4, 9, 16]
>>> for i in s:
...     print(i)
...
1
4
9
16
>>>

Em cada iteração do laço, você obtém um novo item para trabalhar. Este novo valor é colocado na variável de iteração. Neste exemplo, a variável de iteração é x:

for x in s:         ## `x` é uma variável de iteração
    ...statements

Em cada iteração, o valor anterior da variável de iteração é sobrescrito (se houver). Após o término do laço, a variável retém o último valor.

Declaração break (break statement)

Você pode usar a declaração break para sair de um laço antecipadamente.

for name in namelist:
    if name == 'Jake':
        break
    ...
    ...
statements

Quando a declaração break é executada, ela sai do laço e passa para as próximas statements. A declaração break se aplica apenas ao laço mais interno. Se este laço estiver dentro de outro laço, ele não interromperá o laço externo.

Declaração continue (continue statement)

Para pular um elemento e passar para o próximo, use a declaração continue.

for line in lines:
    if line == '\n':    ## Skip blank lines
        continue
    ## More statements
    ...

Isso é útil quando o item atual não é de interesse ou precisa ser ignorado no processamento.

Iterando sobre inteiros (Looping over integers)

Se você precisar contar, use range().

for i in range(100):
    ## i = 0,1,...,99

A sintaxe é range([start,] end [,step])

for i in range(100):
    ## i = 0,1,...,99
for j in range(10,20):
    ## j = 10,11,..., 19
for k in range(10,50,2):
    ## k = 10,12,...,48
    ## Notice how it counts in steps of 2, not 1.
  • O valor final nunca é incluído. Ele espelha o comportamento de fatias (slices).
  • start é opcional. Padrão 0.
  • step é opcional. Padrão 1.
  • range() calcula os valores conforme necessário. Ele não armazena realmente uma grande faixa de números.

Função enumerate()

A função enumerate adiciona um valor de contador extra à iteração.

names = ['Elwood', 'Jake', 'Curtis']
for i, name in enumerate(names):
    ## Loops with i = 0, name = 'Elwood'
    ## i = 1, name = 'Jake'
    ## i = 2, name = 'Curtis'

A forma geral é enumerate(sequence [, start = 0]). start é opcional. Um bom exemplo de uso de enumerate() é rastrear números de linha ao ler um arquivo:

with open(filename) as f:
    for lineno, line in enumerate(f, start=1):
        ...

No final, enumerate é apenas um atalho útil para:

i = 0
for x in s:
    statements
    i += 1

Usar enumerate exige menos digitação e roda um pouco mais rápido.

For e tuplas (tuples)

Você pode iterar com múltiplas variáveis de iteração.

points = [
  (1, 4),(10, 40),(23, 14),(5, 6),(7, 8)
]
for x, y in points:
    ## Loops with x = 1, y = 4
    ##            x = 10, y = 40
    ##            x = 23, y = 14
    ##            ...

Ao usar múltiplas variáveis, cada tupla é desempacotada (unpacked) em um conjunto de variáveis de iteração. O número de variáveis deve corresponder ao número de itens em cada tupla.

Função zip()

A função zip recebe múltiplas sequências e cria um iterador que as combina.

columns = ['name', 'shares', 'price']
values = ['GOOG', 100, 490.1 ]
pairs = zip(columns, values)
## ('name','GOOG'), ('shares',100), ('price',490.1)

Para obter o resultado, você deve iterar. Você pode usar múltiplas variáveis para desempacotar as tuplas, como mostrado anteriormente.

for column, value in pairs:
    ...

Um uso comum de zip é criar pares chave/valor para construir dicionários.

d = dict(zip(columns, values))

Exercício 2.13: Contagem

Experimente alguns exemplos básicos de contagem:

>>> for n in range(10):            ## Count 0 ... 9
        print(n, end=' ')

0 1 2 3 4 5 6 7 8 9
>>> for n in range(10,0,-1):       ## Count 10 ... 1
        print(n, end=' ')

10 9 8 7 6 5 4 3 2 1
>>> for n in range(0,10,2):        ## Count 0, 2, ... 8
        print(n, end=' ')

0 2 4 6 8
>>>

Exercício 2.14: Mais operações de sequência

Experimente interativamente algumas das operações de redução de sequência.

>>> data = [4, 9, 1, 25, 16, 100, 49]
>>> min(data)
1
>>> max(data)
100
>>> sum(data)
204
>>>

Tente iterar sobre os dados.

>>> for x in data:
        print(x)

4
9
...
>>> for n, x in enumerate(data):
        print(n, x)

0 4
1 9
2 1
...
>>>

Às vezes, a instrução for, len() e range() são usadas por iniciantes em algum tipo de fragmento de código horrível que parece ter emergido das profundezas de um programa C enferrujado.

>>> for n in range(len(data)):
        print(data[n])

4
9
1
...
>>>

Não faça isso! Além de fazer os olhos de todos sangrarem ao ler, é ineficiente em termos de memória e roda muito mais lentamente. Apenas use um loop for normal se você quiser iterar sobre os dados. Use enumerate() se precisar do índice por algum motivo.

Exercício 2.15: Um exemplo prático de enumerate()

Lembre-se que o arquivo missing.csv contém dados para uma carteira de ações, mas tem algumas linhas com dados ausentes. Usando enumerate(), modifique seu programa pcost.py para que ele imprima um número de linha com a mensagem de aviso quando encontrar uma entrada ruim.

>>> cost = portfolio_cost('/home/labex/project/missing.csv')
Row 4: Couldn't convert: ['MSFT', '', '51.23']
Row 7: Couldn't convert: ['IBM', '', '70.44']
>>>

Para fazer isso, você precisará alterar algumas partes do seu código.

...
for rowno, row in enumerate(rows, start=1):
    try:
        ...
    except ValueError:
        print(f'Row {rowno}: Bad row: {row}')

Exercício 2.16: Usando a função zip()

No arquivo portfolio.csv, a primeira linha contém os cabeçalhos das colunas. Em todo o código anterior, estávamos descartando-os.

>>> f = open('/home/labex/project/portfolio.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> headers
['name', 'shares', 'price']
>>>

No entanto, e se você pudesse usar os cabeçalhos para algo útil? É aqui que a função zip() entra em cena. Primeiro, tente isso para emparelhar os cabeçalhos do arquivo com uma linha de dados:

>>> row = next(rows)
>>> row
['AA', '100', '32.20']
>>> list(zip(headers, row))
[ ('name', 'AA'), ('shares', '100'), ('price', '32.20') ]
>>>

Observe como zip() emparelhou os cabeçalhos das colunas com os valores das colunas. Usamos list() aqui para transformar o resultado em uma lista para que você possa vê-lo. Normalmente, zip() cria um iterador que deve ser consumido por um loop for.

Este emparelhamento é um passo intermediário para construir um dicionário. Agora tente isto:

>>> record = dict(zip(headers, row))
>>> record
{'price': '32.20', 'name': 'AA', 'shares': '100'}
>>>

Esta transformação é um dos truques mais úteis de se saber ao processar muitos arquivos de dados. Por exemplo, suponha que você queira que o programa pcost.py funcione com vários arquivos de entrada, mas sem levar em consideração o número real da coluna onde o nome, as ações e o preço aparecem.

Modifique a função portfolio_cost() em pcost.py para que ela se pareça com isto:

## pcost.py

def portfolio_cost(filename):
    ...
        for rowno, row in enumerate(rows, start=1):
            record = dict(zip(headers, row))
            try:
                nshares = int(record['shares'])
                price = float(record['price'])
                total_cost += nshares * price
            ## This catches errors in int() and float() conversions above
            except ValueError:
                print(f'Row {rowno}: Bad row: {row}')
        ...

Agora, teste sua função em um arquivo de dados completamente diferente portfoliodate.csv que se parece com isto:

name,date,time,shares,price
"AA","6/11/2007","9:50am",100,32.20
"IBM","5/13/2007","4:20pm",50,91.10
"CAT","9/23/2006","1:30pm",150,83.44
"MSFT","5/17/2007","10:30am",200,51.23
"GE","2/1/2006","10:45am",95,40.37
"MSFT","10/31/2006","12:05pm",50,65.10
"IBM","7/9/2006","3:15pm",100,70.44
>>> portfolio_cost('/home/labex/project/portfoliodate.csv')
44671.15
>>>

Se você fez certo, descobrirá que seu programa ainda funciona, mesmo que o arquivo de dados tenha um formato de coluna completamente diferente do anterior. Isso é legal!

A mudança feita aqui é sutil, mas significativa. Em vez de portfolio_cost() ser codificado para ler um único formato de arquivo fixo, a nova versão lê qualquer arquivo CSV e seleciona os valores de interesse dele. Contanto que o arquivo tenha as colunas necessárias, o código funcionará.

Modifique o programa report.py que você escreveu na Seção 2.3 para que ele use a mesma técnica para selecionar os cabeçalhos das colunas.

Tente executar o programa report.py no arquivo portfoliodate.csv e veja que ele produz a mesma resposta de antes.

Exercício 2.17: Invertendo um dicionário

Um dicionário mapeia chaves para valores. Por exemplo, um dicionário de preços de ações.

>>> prices = {
        'GOOG' : 490.1,
        'AA' : 23.45,
        'IBM' : 91.1,
        'MSFT' : 34.23
    }
>>>

Se você usar o método items(), você pode obter pares (chave, valor):

>>> prices.items()
dict_items([('GOOG', 490.1), ('AA', 23.45), ('IBM', 91.1), ('MSFT', 34.23)])
>>>

No entanto, e se você quisesse obter uma lista de pares (valor, chave) em vez disso? Dica: use zip().

>>> pricelist = list(zip(prices.values(),prices.keys()))
>>> pricelist
[(490.1, 'GOOG'), (23.45, 'AA'), (91.1, 'IBM'), (34.23, 'MSFT')]
>>>

Por que você faria isso? Por um lado, isso permite que você execute certos tipos de processamento de dados nos dados do dicionário.

>>> min(pricelist)
(23.45, 'AA')
>>> max(pricelist)
(490.1, 'GOOG')
>>> sorted(pricelist)
[(23.45, 'AA'), (34.23, 'MSFT'), (91.1, 'IBM'), (490.1, 'GOOG')]
>>>

Isso também ilustra uma característica importante das tuplas. Quando usadas em comparações, as tuplas são comparadas elemento por elemento, começando com o primeiro item. Semelhante a como as strings são comparadas caractere por caractere.

zip() é frequentemente usado em situações como esta, onde você precisa emparelhar dados de lugares diferentes. Por exemplo, emparelhar os nomes das colunas com os valores das colunas para criar um dicionário de valores nomeados.

Observe que zip() não se limita a pares. Por exemplo, você pode usá-lo com qualquer número de listas de entrada:

>>> a = [1, 2, 3, 4]
>>> b = ['w', 'x', 'y', 'z']
>>> c = [0.2, 0.4, 0.6, 0.8]
>>> list(zip(a, b, c))
[(1, 'w', 0.2), (2, 'x', 0.4), (3, 'y', 0.6), (4, 'z', 0.8))]
>>>

Além disso, esteja ciente de que zip() para assim que a sequência de entrada mais curta é esgotada.

>>> a = [1, 2, 3, 4, 5, 6]
>>> b = ['x', 'y', 'z']
>>> list(zip(a,b))
[(1, 'x'), (2, 'y'), (3, 'z')]
>>>

Resumo

Parabéns! Você concluiu o laboratório de Sequências. Você pode praticar mais laboratórios no LabEx para aprimorar suas habilidades.