5. Estruturas de dados
**********************

Esse capítulo descreve algumas coisas que você já aprendeu em detalhes
e adiciona algumas coisas novas também.


5.1. Mais sobre listas
======================

O tipo de dado lista tem ainda mais métodos. Aqui estão apresentados
todos os métodos de objetos do tipo lista:

list.append(x)

   Adiciona um item ao fim da lista. Similar a "a[len(a):] = [x]".

list.extend(iterable)

   Estende a lista, adicionando no fim todos os elementos do argumento
   iterável passado como parâmetro. Similar a "a[len(a):] = iterable".

list.insert(i, x)

   Insere um item em uma dada posição. O primeiro argumento é o índice
   do elemento antes do qual será feita a inserção, assim "a.insert(0,
   x)" insere um elemento na frente da lista e "a.insert(len(a), x)" e
   equivale a "a.append(x)".

list.remove(x)

   Remove o primeiro item encontrado na lista cujo valor é igual a
   *x*. Se não existir valor igual, uma exceção "ValueError" é
   levantada.

list.pop([i])

   Remove o item na posição fornecida na lista e retorna. Se nenhum
   índice for especificado, "a.pop()" remove e retorna o último item
   da lista. Levanta um "IndexError" se a lista estiver vazia ou o
   índice estiver fora do intervalo da lista.

list.clear()

   Remove todos os itens de uma lista. Similar a "del a[:]".

list.index(x[, start[, end]])

   Retorna o índice de base zero da primeira ocorrência de *x* na
   lista. Levanta "ValueError" se não houver tal item.

   Os argumentos opcionais *start* e *end* são interpretados como nas
   notações de fatiamento e são usados para limitar a busca para uma
   subsequência específica da lista. O índice retornado é calculado
   relativo ao começo da sequência inteira e não referente ao
   argumento *start*.

list.count(x)

   Devolve o número de vezes em que *x* aparece na lista.

list.sort(*, key=None, reverse=False)

   Ordena os itens na lista (os argumentos podem ser usados para
   personalizar a ordenação, veja a função "sorted()" para maiores
   explicações).

list.reverse()

   Inverte a ordem dos elementos na lista.

list.copy()

   Devolve uma cópia rasa da lista. Similar a "a[:]".

Um exemplo que usa a maior parte dos métodos das listas:

   >>> frutas = ['laranja', 'maçã', 'pera', 'banana', 'kiwi', 'maçã', 'banana']
   >>> frutas.count('maçã')
   2
   >>> frutas.count('tangerina')
   0
   >>> frutas.index('banana')
   3
   >>> frutas.index('banana', 4) # Encontra a próxima banana iniciando da posição 4
   6
   >>> frutas.reverse()
   >>> frutas
   ['banana', 'maçã', 'kiwi', 'banana', 'pera', 'maçã', 'laranja']
   >>> frutas.append('uva')
   >>> frutas
   ['banana', 'maçã', 'kiwi', 'banana', 'pera', 'maçã', 'laranja', 'uva']
   >>> frutas.sort()
   >>> frutas
   ['maçã', 'maçã', 'banana', 'banana', 'uva', 'kiwi', 'laranja', 'pera']
   >>> frutas.pop()
   'pera'

Você pode ter percebido que métodos como "insert", "remove" ou "sort",
que apenas modificam a lista, não têm valor de retorno impresso --
eles retornam o "None" padrão. [1] Isto é um princípio de design para
todas as estruturas de dados mutáveis em Python.

Outro aspecto que você pode notar é que nem todos os dados podem ser
classificados ou comparados.  Por exemplo, "[None, 'hello', 10]" não é
ordenável porque os inteiros não podem ser comparados a strings e
"None" não pode ser comparado a outros tipos.  Além disso, há alguns
tipos que não têm uma relação de ordenação definida.  Por exemplo,
"3+4j < 5+7j" não é uma comparação válida.


5.1.1. Usando listas como pilhas
--------------------------------

Os métodos de lista tornam muito fácil utilizar listas como pilhas,
onde o item adicionado por último é o primeiro a ser recuperado
(política “último a entrar, primeiro a sair”). Para adicionar um item
ao topo da pilha, use "append()". Para recuperar um item do topo da
pilha use "pop()" sem nenhum índice. Por exemplo:

   >>> pilha = [3, 4, 5]
   >>> pilha.append(6)
   >>> pilha.append(7)
   >>> pilha
   [3, 4, 5, 6, 7]
   >>> pilha.pop()
   7
   >>> pilha
   [3, 4, 5, 6]
   >>> pilha.pop()
   6
   >>> pilha.pop()
   5
   >>> pilha
   [3, 4]


5.1.2. Usando listas como filas
-------------------------------

Você também pode usar uma lista como uma fila, onde o primeiro item
adicionado é o primeiro a ser recuperado (política “primeiro a entrar,
primeiro a sair”); porém, listas não são eficientes para esta
finalidade. Embora *appends* e *pops* no final da lista sejam rápidos,
fazer *inserts* ou *pops* no início da lista é lento (porque todos os
demais elementos têm que ser deslocados).

Para implementar uma fila, use a classe "collections.deque" que foi
projetada para permitir *appends* e *pops* eficientes nas duas
extremidades. Por exemplo:

   >>> from collections import deque
   >>> fila = deque(["Erik", "João", "Miguel"])
   >>> fila.append("Tiago")            # Tiago chega
   >>> fila.append("George")           # George chega
   >>> fila.popleft()                  # O primeiro a chegar agora sai
   'Erik'
   >>> fila.popleft()                  # O segundo a chegar agora sai
   'João'
   >>> fila                            # A fila restante na ordem de chegada
   deque(['Miguel', 'Tiago', 'George'])


5.1.3. Compreensões de lista
----------------------------

Compreensões de lista fornece uma maneira concisa de criar uma lista.
Aplicações comuns são criar novas listas onde cada elemento é o
resultado de alguma operação aplicada a cada elemento de outra
sequência ou iterável, ou criar uma subsequência de elementos que
satisfaçam uma certa condição. (N.d.T. o termo original em inglês é
*list comprehensions*, muito utilizado no Brasil; também se usa a
abreviação *listcomp*).

Por exemplo, suponha que queremos criar uma lista de quadrados, assim:

   >>> quadrados = []
   >>> for x in range(10):
   ...     quadrados.append(x**2)
   ...
   >>> quadrados
   [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Note que isto cria (ou sobrescreve) uma variável chamada "x" que ainda
existe após o término do laço. Podemos calcular a lista dos quadrados
sem qualquer efeito colateral usando:

   quadrados = list(map(lambda x: x**2, range(10)))

ou, de maneira equivalente:

   quadrados = [x**2 for x in range(10)]

que é mais conciso e legível.

Um compreensão de lista consiste de um par de colchetes contendo uma
expressão seguida de uma cláusula "for", e então zero ou mais
cláusulas "for" ou "if". O resultado será uma nova lista resultante da
avaliação da expressão no contexto das cláusulas "for" e "if". Por
exemplo, essa compreensão combina os elementos de duas listas se eles
forem diferentes:

   >>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
   [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

e é equivalente a:

   >>> combos = []
   >>> for x in [1,2,3]:
   ...     for y in [3,1,4]:
   ...         if x != y:
   ...             combos.append((x, y))
   ...
   >>> combos
   [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

Note como a ordem das instruções "for" e "if" é a mesma em ambos os
trechos.

Se a expressão é uma tupla (ex., "(x, y)" no exemplo anterior), ela
deve ser inserida entre parênteses.

   >>> vec = [-4, -2, 0, 2, 4]
   >>> # cria uma nova lista com os valores dobrados
   >>> [x*2 for x in vec]
   [-8, -4, 0, 4, 8]
   >>> # filtra a lista para excluir números negativos
   >>> [x for x in vec if x >= 0]
   [0, 2, 4]
   >>> # aplica uma função para todos os elementos
   >>> [abs(x) for x in vec]
   [4, 2, 0, 2, 4]
   >>> # chama um método em cada elemento
   >>> frutafresca = ['  banana', '  baga-de-logan ', 'maracujá  ']
   >>> [arma.strip() for arma in frutafresca]
   ['banana', 'baga-de-logan', 'maracujá']
   >>> # cria uma lista de tuplas de 2 elementos como (número, quadrado)
   >>> [(x, x**2) for x in range(6)]
   [(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
   >>> # a tupla deve estar entre parênteses, do contrário um erro é levantado
   >>> [x, x**2 for x in range(6)]
     File "<stdin>", line 1
       [x, x**2 for x in range(6)]
        ^^^^^^^
   SyntaxError: did you forget parentheses around the comprehension target?
   >>> # achatamento de uma lista usando uma compreensão de lista com dois 'for'
   >>> vec = [[1,2,3], [4,5,6], [7,8,9]]
   >>> [num for elem in vec for num in elem]
   [1, 2, 3, 4, 5, 6, 7, 8, 9]

Compreensões de lista podem conter expressões complexas e funções
aninhadas:

   >>> from math import pi
   >>> [str(round(pi, i)) for i in range(1, 6)]
   ['3.1', '3.14', '3.142', '3.1416', '3.14159']


5.1.4. Compreensões de lista aninhadas
--------------------------------------

A expressão inicial em uma compreensão de lista pode ser qualquer
expressão arbitrária, incluindo outra compreensão de lista.

Observe este exemplo de uma matriz 3x4 implementada como uma lista de
3 listas de comprimento 4:

   >>> matriz = [
   ...     [1, 2, 3, 4],
   ...     [5, 6, 7, 8],
   ...     [9, 10, 11, 12],
   ... ]

A compreensão de lista abaixo transpõe as linhas e colunas:

   >>> [[linha[i] for linha in matriz] for i in range(4)]
   [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

Como vimos na seção anterior, a compreensão de lista interna é
computada no contexto da cláusula "for" seguinte, portanto o exemplo
acima equivale a:

   >>> transposta = []
   >>> for i in range(4):
   ...     transposta.append([row[i] for linha in matriz])
   ...
   >>> transposta
   [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

e isso, por sua vez, faz o mesmo que isto:

   >>> transposta = []
   >>> for i in range(4):
   ...     # as 3 linhas a seguir implementam uma compreensão de lista aninhada
   ...     linha_transposta = []
   ...     for linha in matriz:
   ...         linha_transposta.append(linha[i])
   ...     transposta.append(linha_transposta)
   ...
   >>> transposta
   [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

Na prática, você deve dar preferência a funções embutidas em vez de
instruções complexas. A função "zip()" resolve muito bem este caso de
uso:

   >>> list(zip(*matriz))
   [(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]

Veja Desempacotando listas de argumentos para entender o uso do
asterisco neste exemplo.


5.2. A instrução "del"
======================

Existe uma maneira de remover um item de uma lista usando seu índice
no lugar do seu valor: a instrução "del". Ela difere do método "pop()"
que devolve um valor. A instrução "del" pode também ser utilizada para
remover fatias de uma lista ou limpar a lista inteira (que fizemos
antes atribuindo uma lista vazia à fatia "a[:]"). Por exemplo:

   >>> a = [-1, 1, 66.25, 333, 333, 1234.5]
   >>> del a[0]
   >>> a
   [1, 66.25, 333, 333, 1234.5]
   >>> del a[2:4]
   >>> a
   [1, 66.25, 1234.5]
   >>> del a[:]
   >>> a
   []

"del" também pode ser usado para remover totalmente uma variável:

   >>> del a

Referenciar a variável "a" depois de sua remoção constitui erro (pelo
menos até que seja feita uma nova atribuição para ela). Encontraremos
outros usos para a instrução "del" mais tarde.


5.3. Tuplas e Sequências
========================

Vimos que listas e strings têm muitas propriedades em comum, como
indexação e operações de fatiamento. Elas são dois exemplos de
*sequências* (veja Tipos sequências --- list, tuple, range). Como
Python é uma linguagem em evolução, outros tipos de sequências podem
ser adicionados. Existe ainda um outro tipo de sequência padrão na
linguagem: a *tupla*.

Uma tupla consiste em uma sequência de valores separados por vírgulas,
por exemplo:

   >>> t = 12345, 54321, 'olá!'
   >>> t[0]
   12345
   >>> t
   (12345, 54321, 'olá!')
   >>> # Tuplas pode ser aninhadas:
   >>> u = t, (1, 2, 3, 4, 5)
   >>> u
   ((12345, 54321, 'olá!'), (1, 2, 3, 4, 5))
   >>> # Tuplas são imutáveis:
   >>> t[0] = 88888
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: 'tuple' object does not support item assignment
   >>> # mas elas podem conter objetos mutáveis:
   >>> v = ([1, 2, 3], [3, 2, 1])
   >>> v
   ([1, 2, 3], [3, 2, 1])

Como você pode ver no trecho acima, na saída do console as tuplas são
sempre envolvidas por parênteses, assim tuplas aninhadas podem ser
lidas corretamente. Na criação, tuplas podem ser envolvidas ou não por
parênteses, desde que o contexto não exija os parênteses (como no caso
da tupla dentro de uma expressão maior). Não é possível atribuir itens
individuais de uma tupla, contudo é possível criar tuplas que
contenham objetos mutáveis, como listas.

Apesar de tuplas serem similares a listas, elas são frequentemente
utilizadas em situações diferentes e com propósitos distintos. Tuplas
são *imutáveis*, e usualmente contém uma sequência heterogênea de
elementos que são acessados via desempacotamento (ver a seguir nessa
seção) ou índice (ou mesmo por um atributo no caso de "namedtuples").
Listas são *mutáveis*, e seus elementos geralmente são homogêneos e
são acessados iterando sobre a lista.

Um problema especial é a criação de tuplas contendo 0 ou 1 itens: a
sintaxe usa certos truques para acomodar estes casos. Tuplas vazias
são construídas por um par de parênteses vazios; uma tupla unitária é
construída por um único valor seguido de uma vírgula (não basta
colocar um único valor entre parênteses). Feio, mas funciona. Por
exemplo:

   >>> vazio = ()
   >>> unitário = 'olá', # <!- note a vírgula ao final
   >>> len(vazio)
   0
   >>> len(unitário)
   1
   >>> unitário
   ('olá',)

A instrução "t = 12345, 54321, 'bom dia!'" é um exemplo de
*empacotamento de tupla*: os valores "12345", "54321" e "'bom dia!'"
são empacotados em uma tupla. A operação inversa também é possível:

   >>> x, y, z = t

Isso é chamado, apropriadamente, de *desempacotamento de sequência* e
funciona para qualquer sequência no lado direito. O desempacotamento
de sequência requer que haja tantas variáveis no lado esquerdo do
sinal de igual, quanto existem de elementos na sequência. Observe que
a atribuição múltipla é, na verdade, apenas uma combinação de
empacotamento de tupla e desempacotamento de sequência.


5.4. Conjuntos
==============

Python também inclui um tipo de dados para conjuntos, chamado "set".
Um conjunto é uma coleção desordenada de elementos, sem elementos
repetidos. Usos comuns para conjuntos incluem a verificação eficiente
da existência de objetos e a eliminação de itens duplicados. Conjuntos
também suportam operações matemáticas como união, interseção,
diferença e diferença simétrica.

Chaves ou a função "set()" podem ser usados para criar conjuntos.
Note: para criar um conjunto vazio você precisa usar "set()", não
"{}"; este último cria um dicionário vazio, uma estrutura de dados que
discutiremos na próxima seção.

Uma pequena demonstração:

   >>> cesta = {'maçã', 'laranja', 'maçã', 'pera', 'laranja', 'banana'}
   >>> print(cesta)                       # mostra que itens duplicados foram removidos
   {'laranja', 'banana', 'pera', 'maçã'}
   >>> 'laranja' in cesta                 # teste de pertinência rápido
   True
   >>> 'crabgrass' in cesta
   False

   >>> # Demonstra operações de conjunto em letras únicas de duas palavras
   >>>
   >>> a = set('abracadabra')
   >>> b = set('alacazam')
   >>> a                                  # letras únicas em a
   {'a', 'r', 'b', 'c', 'd'}
   >>> a - b                              # letras em a, mas não em b
   {'r', 'd', 'b'}
   >>> a | b                              # letras em a ou em b ou ambos
   {'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
   >>> a & b                              # letras em ambos a e b
   {'a', 'c'}
   >>> a ^ b                              # letras em a ou b, mas não em ambos
   {'r', 'd', 'b', 'm', 'z', 'l'}

Da mesma forma que compreensão de listas, compreensões de conjunto
também são suportadas:

   >>> a = {x for x in 'abracadabra' if x not in 'abc'}
   >>> a
   {'r', 'd'}


5.5. Dicionários
================

Outra estrutura de dados embutida muito útil em Python é o
*dicionário*, cujo tipo é "dict" (ver Tipo mapeamento --- dict).
Dicionários são também chamados de “memória associativa” ou “vetor
associativo” em outras linguagens. Diferente de sequências que são
indexadas por inteiros, dicionários são indexados por chaves (*keys*),
que podem ser de qualquer tipo imutável (como strings e inteiros).
Tuplas também podem ser chaves se contiverem apenas strings, inteiros
ou outras tuplas. Se a tupla contiver, direta ou indiretamente,
qualquer valor mutável, não poderá ser chave. Listas não podem ser
usadas como chaves porque podem ser modificadas *internamente* pela
atribuição em índices ou fatias, e por métodos como "append()" e
"extend()".

Um bom modelo mental é imaginar um dicionário como um conjunto não-
ordenado de pares *chave:valor*, onde as chaves são únicas em uma dada
instância do dicionário. Dicionários são delimitados por chaves: "{}",
e contém uma lista de pares chave:valor separada por vírgulas. Dessa
forma também será exibido o conteúdo de um dicionário no console do
Python. O dicionário vazio é "{}".

As principais operações em um dicionário são armazenar e recuperar
valores a partir de chaves. Também é possível remover um par
*chave:valor* com o comando "del". Se você armazenar um valor
utilizando uma chave já presente, o antigo valor será substituído pelo
novo.

Extrair um valor para uma chave inexistente por meio de um índice
("d[key]") levanta "KeyError". Para evitar esse erro ao tentar acessar
uma chave possivelmente inexistente, use o método "get()", que retorna
"None" (ou um valor padrão especificado) se a chave não estiver no
dicionário.

Executar "list(d)" em um dicionário devolve a lista de todas as chaves
presentes no dicionário, na ordem de inserção (se desejar ordená-las
basta usar a função "sorted(d)"). Para verificar a existência de uma
chave, use o operador "in".

A seguir, um exemplo de uso do dicionário:

   >>> tel = {'jack': 4098, 'sape': 4139}
   >>> tel['guido'] = 4127
   >>> tel
   {'jack': 4098, 'sape': 4139, 'guido': 4127}
   >>> tel['jack']
   4098
   >>> tel['irv']
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   KeyError: 'irv'
   >>> print(tel.get('irv'))
   None
   >>> del tel['sape']
   >>> tel['irv'] = 4127
   >>> tel
   {'jack': 4098, 'guido': 4127, 'irv': 4127}
   >>> list(tel)
   ['jack', 'guido', 'irv']
   >>> sorted(tel)
   ['guido', 'irv', 'jack']
   >>> 'guido' in tel
   True
   >>> 'jack' not in tel
   False

O construtor "dict()" produz dicionários diretamente de sequências de
pares chave-valor:

   >>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
   {'sape': 4139, 'guido': 4127, 'jack': 4098}

Além disso, as compreensões de dicionários podem ser usadas para criar
dicionários a partir de expressões arbitrárias de chave e valor:

   >>> {x: x**2 for x in (2, 4, 6)}
   {2: 4, 4: 16, 6: 36}

Quando chaves são strings simples, é mais fácil especificar os pares
usando argumentos nomeados no construtor:

   >>> dict(sape=4139, guido=4127, jack=4098)
   {'sape': 4139, 'guido': 4127, 'jack': 4098}


5.6. Técnicas de iteração
=========================

Ao iterar sobre dicionários, a chave e o valor correspondente podem
ser obtidos simultaneamente usando o método "items()".

   >>> cavaleiros = {'gallahad': 'o puro', 'robin': 'o bravo'}
   >>> for k, v in cavaleiros.items():
   ...     print(k, v)
   ...
   gallahad o puro
   robin o bravo

Ao iterar sobre sequências, a posição e o valor correspondente podem
ser obtidos simultaneamente usando a função "enumerate()".

   >>> for i, v in enumerate(['jogo', 'da', 'velha']):
   ...     print(i, v)
   ...
   0 jogo
   1 da
   2 velha

Para percorrer duas ou mais sequências ao mesmo tempo, as entradas
podem ser pareadas com a função "zip()".

   >>> perguntas = ['Nome', 'Missão', 'Cor favorita']
   >>> respostas = ['Lancelot', 'o santo graal', 'azul']
   >>> for q, a in zip(perguntas, respostas):
   ...     print('{0}? É {1}.'.format(q, a))
   ...
   Nome? É Lancelot.
   Missão? É o santo graal.
   Cor favorita? É azul.

Para percorrer uma sequência em ordem inversa, chame a função
"reversed()" com a sequência na ordem original.

   >>> for i in reversed(range(1, 10, 2)):
   ...     print(i)
   ...
   9
   7
   5
   3
   1

Para percorrer uma sequência de maneira ordenada, use a função
"sorted()", que retorna uma lista ordenada com os itens, mantendo a
sequência original inalterada.

   >>> cesta = ['maçã', 'laranja', 'maçã', 'pera', 'laranja', 'banana']
   >>> for i in sorted(cesta):
   ...     print(i)
   ...
   maçã
   maçã
   banana
   laranja
   laranja
   pera

Usar "set()" em uma sequência elimina elementos duplicados. O uso de
"sorted()" em combinação com "set()" sobre uma sequência é uma maneira
idiomática de fazer um loop sobre elementos exclusivos da sequência na
ordem de classificação.

   >>> cesta = ['maçã', 'laranja', 'maçã', 'pera', 'laranja', 'banana']
   >>> for i in sorted(cesta):
   ...     print(i)
   ...
   maçã
   maçã
   banana
   laranja
   laranja
   pera

Às vezes é tentador alterar uma lista enquanto você itera sobre ela;
porém, costuma ser mais simples e seguro criar uma nova lista.

   >>> import math
   >>> dados_brutos = [56.2, float('NaN'), 51.7, 55.3, 52.5, float('NaN'), 47.8]
   >>> dados_filtrados = []
   >>> for valor in dados_brutos:
   ...     if not math.isnan(value):
   ...         dados_filtrados.append(valor)
   ...
   >>> dados_filtrados
   [56.2, 51.7, 55.3, 52.5, 47.8]


5.7. Mais sobre condições
=========================

As condições de controle usadas nas instruções "while" e "if" podem
conter quaisquer operadores, não apenas comparações.

Os operadores de comparação "in" e "not in" fazem testes de inclusão
que determinam se um valor está (ou não está) em um contêiner. Os
operadores "is" e "is not" comparam se dois objetos são realmente o
mesmo objeto.  Todos os operadores de comparação possuem a mesma
prioridade, que é menor que a prioridade de todos os operadores
numéricos.

Comparações podem ser encadeadas: Por exemplo "a < b == c" testa se
"a" é menor que "b" e também se "b" é igual a "c".

Comparações podem ser combinadas através de operadores booleanos "and"
e "or", e o resultado de uma comparação (ou de qualquer outra
expressão), pode ter seu valor booleano negado através de "not". Estes
possuem menor prioridade que os demais operadores de comparação. Entre
eles, "not" é o de maior prioridade e "or" o de menor. Dessa forma, a
condição "A and not B or C" é equivalente a "(A and (not B)) or C".
Naturalmente, parênteses podem ser usados para expressar o agrupamento
desejado.

Os operadores booleanos "and" e "or" são operadores *curto-circuito*:
seus argumentos são avaliados da esquerda para a direita, e a
avaliação encerra quando o resultado é determinado. Por exemplo, se
"A" e "C" são expressões verdadeiras, mas "B" é falsa, então "A and B
and C" não chega a avaliar a expressão "C". Em geral, quando usado
sobre valores genéricos e não como booleanos, o valor do resultado de
um operador curto-circuito é o último valor avaliado na expressão.

É possível atribuir o resultado de uma comparação ou outra expressão
booleana para uma variável. Por exemplo:

   >>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
   >>> non_null = string1 or string2 or string3
   >>> non_null
   'Trondheim'

Observe que no Python, ao contrário de C, a atribuição dentro de
expressões deve ser feita explicitamente com o operador morsa ":=".
Isso evita uma classe comum de problemas encontrados nos programas C:
digitar "=" em uma expressão quando "==" era o planejado.


5.8. Comparando sequências e outros tipos
=========================================

Objetos sequência podem ser comparados com outros objetos sequência,
desde que o tipo das sequências seja o mesmo. A comparação utiliza a
ordem lexicográfica: primeiramente os dois primeiros itens são
comparados, e se diferirem isto determinará o resultado da comparação,
caso contrário os próximos dois itens serão comparados, e assim por
diante até que se tenha exaurido alguma das sequências. Se em uma
comparação de itens, os mesmos forem também sequências (aninhadas),
então é disparada recursivamente outra comparação lexicográfica. Se
todos os itens da sequência forem iguais, então as sequências são
ditas iguais. Se uma das sequências é uma subsequência da outra, então
a subsequência é a menor. A comparação lexicográfica de strings
utiliza codificação Unicode para definir a ordenação. Alguns exemplos
de comparações entre sequências do mesmo tipo:

   (1, 2, 3)              < (1, 2, 4)
   [1, 2, 3]              < [1, 2, 4]
   'ABC' < 'C' < 'Pascal' < 'Python'
   (1, 2, 3, 4)           < (1, 2, 4)
   (1, 2)                 < (1, 2, -1)
   (1, 2, 3)             == (1.0, 2.0, 3.0)
   (1, 2, ('aa', 'ab'))   < (1, 2, ('abc', 'a'), 4)

Note que comparar objetos de tipos diferentes com "<" ou ">" é
permitido desde que os objetos possuam os métodos de comparação
apropriados. Por exemplo, tipos numéricos mistos são comparados de
acordo com os seus valores numéricos, portanto 0 é igual a 0.0, etc.
Em caso contrário, ao invés de fornecer uma ordenação arbitrária, o
interpretador levantará um "TypeError".

-[ Notas de rodapé ]-

[1] Outras linguagens podem retornar o objeto modificado, o que
    permite encadeamento de métodos, como
    "d->insert("a")->remove("b")->sort();".
