sábado, 8 de maio de 2021

Tipos de dados nativos em Python

❝A admiração é o fundamento de toda filosofia, a investigação de seu progresso, a ignorância seu fim.❞
- Michel de Montaigne

Mergulho

Tipos de dados. Deixe de lado seu primeiro programa Python por apenas um minuto e vamos falar sobre tipos de dados. Em Python, todo valor tem um tipo de dados, mas você não precisa declarar o tipo de dados das variáveis. Como isso funciona? Com base na atribuição original de cada variável, o Python descobre que tipo é e mantém o controle disso internamente.

Python tem muitos tipos de dados nativos. Aqui estão os mais importantes:

  1. Os booleanos são True ou False.
  2. Os números podem ser inteiros (1 e 2), flutuantes (1.1 e 1.2), frações (1/2 e 2/3) ou até mesmo números complexos.
  3. Strings são sequências de caracteres Unicode, por exemplo, um documento HTML.
  4. Bytes e matrizes de bytes, por exemplo, um arquivo de imagem JPEG.
  5. As listas são sequências ordenadas de valores.
  6. As tuplas são sequências ordenadas e imutáveis de valores.
  7. Conjuntos (sets) são pacotes não ordenados de valores.
  8. Os dicionários são pacotes não ordenados de pares de chave-valores.

Claro, existem mais tipos do que estes. Tudo é um objeto em Python, portanto, existem tipos como módulo, função, classe , método, arquivo e até mesmo código compilado. Você já viu alguns destes: módulos têm nomes, funções têm docstrings. Você aprenderá sobre classes em Classes & Iterators e sobre arquivos em Files.

Strings e bytes são importantes o suficiente - e complicados o suficiente - para que tenham seu próprio capítulo. Vamos examinar os outros primeiro.

Boolean

Você pode usar virtualmente qualquer expressão em um contexto booleano.

Os booleanos são verdadeiros ou falsos. Python tem duas constantes, habilmente nomeadas True e False, que podem ser usadas para atribuir valores booleanos diretamente. As expressões também podem ser avaliadas como um valor booleano. Em certos lugares (como instruções if), Python espera que uma expressão seja avaliada como um valor booleano. Esses locais são chamados de contextos booleanos. Você pode usar virtualmente qualquer expressão em um contexto booleano, e o Python tentará determinar seu valor verdadeiro. Tipos de dados diferentes têm regras diferentes sobre quais valores são verdadeiros ou falsos em um contexto booleano. (Isso fará mais sentido quando você ver alguns exemplos concretos posteriormente neste capítulo.)

Por exemplo, pegue este snippet de teste.py:

if tamanho < 0:
    raise ValueError('O número não deve ser negativo')

tamanho é um número inteiro, 0 é um número inteiro e < é um operador numérico. O resultado da expressão tamanho < 0 é sempre um booleano. Você mesmo pode testar isso no shell interativo do Python:

>>> tamanho = 1
>>> tamanho < 0
False
>>> tamanho = 0
>>> tamanho < 0
False
>>> tamanho = -1
>>> tamanho < 0
True

Devido a alguns problemas herdados que sobraram do Python 2, os booleanos podem ser tratados como números. True é 1; False é 0.

>>> True + True
2
>>> True - False
1
>>> True * False
0
>>> True / False
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

Eca, Eca, Eca! Não faça isso. Esqueça que eu mencionei isso.

Números

Os números são fantásticos. Existem tantos para escolher Python suporta tanto números inteiros quanto números de ponto flutuante. Não há declaração de tipo para distingui-los; Python os diferencia pela presença ou ausência de um ponto decimal.

>>> type(1)                 ①
<class 'int'>
>>> isinstance(1, int)      ②
True
>>> 1 + 1                   ③
2
>>> 1 + 1.0                 ④
2.0
>>> type(2.0)
<class 'float'>
  1. Você pode usar a função type() para verificar o tipo de qualquer valor ou variável. Como você pode esperar, 1 é um int.
  2. Da mesma forma, você pode usar a função isinstance() para verificar se um valor ou variável é de um determinado tipo.
  3. Adicionar um int a um int resulta em um int.
  4. Adicionando um int a um float resulta um float. Python força o int num float para realizar a adição e retorna um float como resultado.

Coagindo inteiros para floats e vice-versa

Como você acabou de ver, alguns operadores (como adição) forçarão os inteiros a números de ponto flutuante conforme necessário. Você também pode coagi-los por si mesmo.

>>> float(2)                ①
2.0
>>> int(2.0)                ②
2
>>> int(2.5)                ③
2
>>> int(-2.5)               ④
-2
>>> 1.12345678901234567890  ⑤
1.1234567890123457
>>> type(1000000000000000)  ⑥
<class 'int'>
  1. Você pode coagir explicitamente um int para um float chamando a função float().
  2. Sem surpresa, você também pode coagir a float para um int chamando a função int().
  3. A função int() irá truncar, não arredondar.
  4. A função int() trunca números negativos em direção a 0. É uma verdadeira função de truncar, não uma função de piso.
  5. Os números de vírgula flutuante têm precisão de 15 casas decimais.
  6. Os inteiros podem ser arbitrariamente grandes.

Observação

Python 2 tinha tipos separados para int e long. O tipo de dados int era limitado por sys.maxint, que variava de acordo com a plataforma, mas geralmente era 232-1. O Python 3 tem apenas um tipo inteiro, que se comporta principalmente como o tipo antigo long do Python 2. Consulte PEP 237 para obter detalhes.

Operações Numéricas Comuns

Você pode fazer todo tipo de coisa com números.

>>> 11 / 2      ①
5.5
>>> 11 // 2     ②
5
>>> −11 // 2    ③
−6
>>> 11.0 // 2   ④
5.0
>>> 11 ** 2     ⑤
121
>>> 11 % 2      ⑥
1
  1. O operador / realiza a divisão de ponto flutuante. Ele retorna um float mesmo se o numerador e o denominador forem ints.
  2. O operador // realiza um tipo peculiar de divisão inteira. Quando o resultado for positivo, você pode pensar nisso como um truncamento (não um arredondamento) para 0 casas decimais, mas tenha cuidado com isso.
  3. Ao dividir números negativos inteiros, o operador // arredonda “para cima” para o inteiro mais próximo. Matematicamente falando, está arredondando “para baixo”, uma vez que −6 é menor que −5, mas pode truncá-lo se você estiver esperando que trunque para −5.
  4. O operador // nem sempre retorna um número inteiro. Se o numerador ou denominador for um float, ele ainda será arredondado para o inteiro mais próximo, mas o valor de retorno real será um float.
  5. O operador ** significa "elevado à potência de." 112 é 121.
  6. O operador % dá o resto após realizar a divisão inteira. 11 dividido por 2 é 5 com um resto de 1, então o resultado aqui é 1.

Observação

No Python 2, o operador / geralmente significa divisão inteira, mas você pode fazer com que ele se comporte como uma divisão de ponto flutuante incluindo uma diretiva especial em seu código. No Python 3, o operador / sempre significa divisão de ponto flutuante. Veja PEP 238 para detalhes.

Frações

Python não se limita a números inteiros e números de ponto flutuante. Ele também pode fazer toda a matemática sofisticada que você aprendeu no colégio e rapidamente se esqueceu dela.

>>> import fractions              ①
>>> x = fractions.Fraction(1, 3)  ②
>>> x
Fraction(1, 3)
>>> x * 2                         ③
Fraction(2, 3)
>>> fractions.Fraction(6, 4)      ④
Fraction(3, 2)
>>> fractions.Fraction(0, 0)      ⑤
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "fractions.py", line 96, in __new__
    raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
ZeroDivisionError: Fraction(0, 0)
  1. Para começar a usar frações, importe o módulo fractions.
  2. Para definir uma fração, crie um objeto Fraction e passe o numerador e o denominador.
  3. Você pode realizar todas as operações matemáticas usuais com frações. As operações retornam um novo objeto Fraction. 2 * (1/3) = (2/3)
  4. O objeto Fraction irá reduzir automaticamente as frações. (6/4) = (3/2)
  5. Python tem o bom senso de não criar uma fração com denominador zero.

Trigonometria

Você também pode fazer trigonometria básica em Python.

>>> import math
>>> math.pi                ①
3.1415926535897931
>>> math.sin(math.pi / 2)  ②
1.0
>>> math.tan(math.pi / 4)  ③
0.99999999999999989
  1. O módulo math tem uma constante para π, a razão entre a circunferência de um círculo e seu diâmetro.
  2. O módulo math possui todas as funções trigonométricas básicas, incluindo sin(), cos(), tan(), e variantes como asin().
  3. Observe, no entanto, que Python não tem precisão infinita. tan(π / 4) deve retornar 1.0, não 0.99999999999999989.

Números em um contexto booleano

Os valores zero são falsos e os valores diferentes de zero são verdadeiros.

Você pode usar números em um contexto booleano, como uma instrução if. Os valores zero são falsos e os valores diferentes de zero são verdadeiros.

>>> def eh_verdadeiro(qualquer_coisa):       ①
...   if qualquer_coisa:
...     print("sim, é verdadeiro")
...   else:
...     print("não, é verdadeiro")
...
>>> eh_verdadeiro(1)                         ②
sim, é verdadeiro
>>> eh_verdadeiro(-1)
sim, é verdadeiro
>>> eh_verdadeiro(0)
não, é verdadeiro
>>> eh_verdadeiro(0.1)                       ③
sim, é verdadeiro
>>> eh_verdadeiro(0.0)
não, é verdadeiro
>>> import fractions
>>> eh_verdadeiro(fractions.Fraction(1, 2))  ④
sim, é verdadeiro
>>> eh_verdadeiro(fractions.Fraction(0, 1))
não, é verdadeiro
  1. Você sabia que pode definir suas próprias funções no shell interativo do Python? Basta pressionar ENTER no final de cada linha e ENTER em uma linha em branco para finalizar.
  2. Em um contexto booleano, inteiros diferentes de zero são verdadeiros; 0 é falso.
  3. Números de ponto flutuante diferentes de zero são verdadeiros; 0.0 é falso. Tenha cuidado com este! Se houver o menor erro de arredondamento (não impossível, como você viu na seção anterior), o Python estará testando 0.0000000000001 em vez de 0 e retornará True.
  4. As frações também podem ser usadas em um contexto booleano. Fraction(0, n) é falso para todos os valores de n. Todas as outras frações são verdadeiras.

Listas

Listas são o tipo de dados mais poderoso do Python. Quando eu digo “ lista ”, você pode estar pensando “array cujo tamanho que tem que declarar de antemão, que só pode conter itens do mesmo tipo, e c.” Não pense assim. Listas são muito mais legais do que isso.

Observação

Uma lista em Python é como um array em Perl 5. No Perl 5, as variáveis que armazenam arrays sempre começam com o caractere @; no Python, as variáveis podem ter qualquer nome, e o Python rastreia o tipo de dados internamente.

Observação

Uma lista em Python é muito mais do que um array em Java (embora possa ser usada como um se isso for realmente tudo o que você deseja da vida). Uma analogia melhor seria com a classe ArrayList, que pode conter objetos arbitrários e pode se expandir dinamicamente à medida que novos itens são adicionados.

Criando uma lista

Criar uma lista é fácil: use colchetes para envolver uma lista de valores separados por vírgulas.

>>> uma_lista = ['a', 'b', 'mpilgrim', 'z', 'example']  ①
    >>> uma_lista
['a', 'b', 'mpilgrim', 'z', 'example']
>>> uma_lista[0]                                        ②
'a'
>>> uma_lista[4]                                        ③
'example'
>>> uma_lista[-1]                                       ④
'example'
>>> uma_lista[-3]                                       ⑤
'mpilgrim'
  1. Primeiro, você define uma lista de cinco itens. Observe que eles mantêm sua ordem original. Isso não é um acidente. Uma lista é um set ordenado de itens.
  2. Uma lista pode ser usada como uma matriz baseada em zero. O primeiro item de qualquer lista não vazia é sempre uma_lista[0].
  3. O último item desta lista de cinco itens é uma_lista[4] porque as listas são sempre baseadas em zero.
  4. Um índice negativo acessa itens do final da lista em contagem regressiva. O último item de qualquer lista não vazia é sempre uma_lista[-1].
  5. Se o índice negativo é confuso para você, pense nisso desta maneira: uma_lista[-n] == uma_lista[len(uma_lista) - n]. Portanto, nesta lista, uma_lista[-3] == uma_lista[5 - 3] == uma_lista[2].

Slicing uma lista

uma_lista[0] é o primeiro item de uma_lista.

Depois de definir uma lista, você pode obter qualquer parte dela como uma nova lista. Isso é chamado de fatiar (slicing) a lista.

>>> uma_lista
['a', 'b', 'mpilgrim', 'z', 'example']
>>> uma_lista[1:3]            ①
['b', 'mpilgrim']
>>> uma_lista[1:-1]           ②
['b', 'mpilgrim', 'z']
>>> uma_lista[0:3]            ③
['a', 'b', 'mpilgrim']
>>> uma_lista[:3]             ④
['a', 'b', 'mpilgrim']
>>> uma_lista[3:]             ⑤
['z', 'example']
>>> uma_lista[:]              ⑥
['a', 'b', 'mpilgrim', 'z', 'example']
  1. Você pode obter uma parte de uma lista, chamada de “slicing”, especificando dois índices. O valor de retorno é uma nova lista contendo todos os itens da lista, em ordem, começando com o índice da primeira fatia (neste caso uma_lista[1]), até mas não incluindo o índice da segunda fatia (neste caso uma_lista[3]).
  2. O fatiamento funciona se um ou ambos os índices de fatia forem negativos. Se ajudar, você pode pensar da seguinte maneira: lendo a lista da esquerda para a direita, o índice da primeira fatia especifica o primeiro item que você deseja e o índice da segunda fatia especifica o primeiro item que você não deseja. O valor de retorno é tudo o que está entre os dois.
  3. As listas são baseadas em zero, portanto, uma_lista[0:3] retorna os três primeiros itens da lista, começando em uma_lista[0], até, mas não incluindo uma_lista[3].
  4. Se o índice da fatia esquerda for 0, você pode deixá-lo de fora e 0 está implícito. Então uma_lista[:3] é o mesmo que uma_lista[0:3], porque o 0 inicial está implícito.
  5. Da mesma forma, se o índice de fatia correto for o comprimento da lista, você pode deixá-lo de fora. Então uma_lista[3:] é o mesmo que uma_lista[3:5], porque essa lista tem cinco itens. Há uma simetria agradável aqui. Nesta lista de cinco itens, uma_lista[:3] retorna os 3 primeiros itens e uma_lista[3:] retorna os dois últimos itens. Na verdade, uma_lista[:n] sempre retornará os primeiros n itens e uma_lista[n:] retornará os demais, independentemente do comprimento da lista.
  6. Se ambos os índices de fatias forem omitidos, todos os itens da lista serão incluídos. Mas isso não é o mesmo que a variável uma_lista original. É uma nova lista que contém todos os mesmos itens. uma_lista[:] é uma abreviatura para fazer uma cópia completa de uma lista.

Adicionando itens a uma lista

Existem quatro maneiras de adicionar itens a uma lista.

>>> uma_lista = ['a']
>>> uma_lista = uma_lista + [2.0, 3] ①
>>> uma_lista                        ②
['a', 2.0, 3]
>>> uma_lista.append(True)           ③
>>> uma_lista
['a', 2.0, 3, True]
>>> uma_lista.extend(['four', 'Ω'])  ④
>>> uma_lista
['a', 2.0, 3, True, 'four', 'Ω']
>>> uma_lista.insert(0, 'Ω')         ⑤
>>> uma_lista
['Ω', 'a', 2.0, 3, True, 'four', 'Ω']
  1. O operador + concatena listas para criar uma nova lista. Uma lista pode conter qualquer número de itens; não há limite de tamanho (além da memória disponível). No entanto, se a memória for uma preocupação, você deve estar ciente de que a concatenação de lista cria uma segunda lista na memória. Nesse caso, essa nova lista é imediatamente atribuída à variável uma_lista existente. Portanto, essa linha de código é realmente um processo de duas etapas - concatenação e atribuição - que pode (temporariamente) consumir muita memória quando você está lidando com listas grandes.
  2. Uma lista pode conter itens de qualquer tipo de dados, e os itens em uma única lista não precisam ser todos do mesmo tipo. Aqui temos uma lista contendo uma string, um número de ponto flutuante e um inteiro.
  3. O método append() adiciona um único item ao final da lista. (Agora temos quatro tipos de dados diferentes na lista!).
  4. As listas são implementadas como classes. “Criar” uma lista é, na verdade, instanciar uma classe. Como tal, uma lista possui métodos que operam nela. O método extend() pega um argumento, uma lista, e acrescenta cada um dos itens do argumento à lista original.
  5. O método insert() insere um único item em uma lista. O primeiro argumento é o índice do primeiro item da lista que sairá da posição. Os itens da lista não precisam ser exclusivos; por exemplo, agora existem dois itens separados com o valor 'Ω': o primeiro item uma_lista[0], e o último item uma_lista[6].

Observação

uma_lista.insert(0, value) é como a função unshift() em Perl. Ele adiciona um item ao início da lista e todos os outros itens têm seu índice posicional aumentado para abrir espaço.

Vamos examinar mais de perto a diferença entre append() e extend().

>>> uma_lista = ['a', 'b', 'c']
>>> uma_lista.extend(['d', 'e', 'f'])  ①
>>> uma_lista
['a', 'b', 'c', 'd', 'e', 'f']
>>> len(uma_lista)                     ②
6
>>> uma_lista[-1]
'f'
>>> uma_lista.append(['g', 'h', 'i'])  ③
>>> uma_lista
['a', 'b', 'c', 'd', 'e', 'f', ['g', 'h', 'i']]
>>> len(uma_lista)                     ④
7
>>> uma_lista[-1]
['g', 'h', 'i']
  1. O método extend() recebe um único argumento, que é sempre uma lista, e adiciona cada um dos itens dessa lista a uma_lista.
  2. Se você começar com uma lista de três itens e estendê-la com uma lista de outros três itens, terminará com uma lista de seis itens.
  3. Por outro lado, o método append() recebe um único argumento, que pode ser qualquer tipo de dados. Aqui, você está chamando o método append() com uma lista de três itens.
  4. Se você começar com uma lista de seis itens e anexar uma lista a ela, acabará com... uma lista de sete itens. Por que sete? Porque o último item (que você acabou de anexar) é uma lista. As listas podem conter qualquer tipo de dados, incluindo outras listas. Isso pode ser o que você deseja ou não. Mas é o que você pediu e é o que você conseguiu.

Pesquisa de valores em uma lista

>>> uma_lista = ['a', 'b', 'new', 'mpilgrim', 'new']
>>> uma_lista.count('new')       ①
2
>>> 'new' in uma_lista           ②
True
>>> 'c' in uma_lista
False
>>> uma_lista.index('mpilgrim')  ③
3
>>> uma_lista.index('new')       ④
2
>>> uma_lista.index('c')         ⑤
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
ValueError: list.index(x): x not in list
  1. Como você pode esperar, o método count() retorna o número de ocorrências de um valor específico em uma lista.
  2. Se tudo o que você deseja saber é se um valor está na lista ou não, o operador in é um pouco mais rápido do que usar o método count(). O operador in sempre retorna True ou False; ele não dirá quantas vezes o valor aparece na lista.
  3. Nem o operador in nem o método count() informarão onde um valor aparece na lista. Se você precisa saber em que lugar da lista está um valor, chame o método index(). Por padrão, ele pesquisará a lista inteira, embora você possa especificar um segundo argumento opcional do índice (baseado em 0) para começar, e até mesmo um terceiro argumento opcional do índice (baseado em 0) para parar a pesquisa.
  4. O método index() encontra a primeira ocorrência de um valor na lista. Nesse caso, ocorre 'new' duas vezes na lista, em uma_lista[2] e uma_lista[4], mas o método index() retornará apenas o índice da primeira ocorrência.
  5. Como você não pode esperar, se o valor não for encontrado na lista, o método index() gerará uma exceção.

Espere o que? Isso mesmo: o método index() levanta uma exceção se não encontrar o valor na lista. Isso é notavelmente diferente da maioria das linguagens, que retornará algum índice inválido (como -1). Embora isso possa parecer irritante no início, acho que você vai gostar. Isso significa que seu programa irá travar na origem do problema, em vez de falhar estranha e silenciosamente mais tarde. Lembre-se, -1 é um índice de lista válido. Se o método index() retornasse -1, isso poderia levar a algumas sessões de depuração não tão divertidas!

Removendo itens de uma lista

As listas nunca têm lacunas.

As listas podem se expandir e se contrair automaticamente. Você viu a parte da expansão. Existem várias maneiras diferentes de remover itens de uma lista também.

>>> uma_lista = ['a', 'b', 'new', 'mpilgrim', 'new']
>>> uma_lista[1]
'b'
>>> del uma_lista[1]         ①
>>> uma_lista
['a', 'new', 'mpilgrim', 'new']
>>> uma_lista[1]             ②
'new'
  1. Você pode usar a instrução del para excluir um item específico de uma lista.
  2. Acessando o índice 1 após a exclusão do índice 1 não resultar em um erro. Todos os itens após o item excluído mudam seu índice posicional para “preencher a lacuna” criada ao excluir o item.

Não sabe o índice posicional? Não é um problema; você pode remover itens por valor.

>>> uma_lista.remove('new')  ①
>>> uma_lista
['a', 'mpilgrim', 'new']
>>> uma_lista.remove('new')  ②
>>> uma_lista
['a', 'mpilgrim']
>>> uma_lista.remove('new')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
  1. Você também pode remover um item de uma lista com o método remove(). O método remove() pega um valor e remove a primeira ocorrência desse valor da lista. Novamente, todos os itens após o item excluído terão seus índices de posição reduzidos para "preencher a lacuna". As listas nunca têm lacunas.
  2. Você pode chamar o método remove() com a frequência que desejar, mas isso gerará uma exceção se você tentar remover um valor que não está na lista.

Removendo itens de uma lista: Rodada bônus

Outro método de lista interessante é o pop(). O método pop() é outra maneira de remover itens de uma lista, mas com uma diferença.

>>> uma_lista = ['a', 'b', 'new', 'mpilgrim']
>>> uma_lista.pop()   ①
'mpilgrim'
>>> uma_lista
['a', 'b', 'new']
>>> uma_lista.pop(1)  ②
'b'
>>> uma_lista
['a', 'new']
>>> uma_lista.pop()
'new'
>>> uma_lista.pop()
'a'
>>> uma_lista.pop()   ③
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: pop from empty list
  1. Quando chamado sem argumentos, o método pop() da lista remove o último item da lista e retorna o valor removido.
  2. Você pode destacar itens arbitrários de uma lista. Basta passar um índice posicional para o método pop(). Ele removerá esse item, mudará todos os itens posteriores para “preencher a lacuna” e retornará o valor removido.
  3. Chamar pop() numa lista vazia gera uma exceção.

Observação

Chamar o método pop() da lista sem um argumento é como a função pop() em Perl. Ele remove o último item da lista e retorna o valor do item removido. Perl tem outra função, shift(), que remove o primeiro item e retorna seu valor; em Python, isso é equivalente a uma_lista.pop(0).

Listas em um contexto booleano

Listas vazias são falsas; todas as outras listas são verdadeiras.

Você também pode usar uma lista em um contexto booleano, como uma instrução if.

>>> def eh_verdadeiro(qualquer_coisa):
...   if qualquer_coisa:
...     print("sim, é verdadeiro")
...   else:
...     print("não, é verdadeiro")
...
>>> eh_verdadeiro([])             ①
não, é verdadeiro
>>> eh_verdadeiro(['a'])          ②
sim, é verdadeiro
>>> eh_verdadeiro([False])        ③
sim, é verdadeiro
  1. Em um contexto booleano, uma lista vazia é falsa.
  2. Qualquer lista com pelo menos um item é verdadeira.
  3. Qualquer lista com pelo menos um item é verdadeira. O valor dos itens é irrelevante.

Tuplas

Uma tupla é uma lista imutável. Uma tupla não pode ser alterada de nenhuma maneira depois de criada.

>>> a_tuple = ("a", "b", "mpilgrim", "z", "example")  ①
>>> a_tuple
('a', 'b', 'mpilgrim', 'z', 'example')
>>> a_tuple[0]                                        ②
'a'
>>> a_tuple[-1]                                       ③
'example'
>>> a_tuple[1:3]                                      ④
('b', 'mpilgrim')
  1. Uma tupla é definida da mesma maneira que uma lista, exceto que todo o set de elementos é colocado entre parênteses em vez de colchetes.
  2. Os elementos de uma tupla têm uma ordem definida, assim como uma lista. Os índices de tupla são baseados em zero, assim como uma lista, então o primeiro elemento de uma tupla não vazia é sempre a_tuple[0].
  3. Índices negativos contam a partir do final da tupla, assim como uma lista.
  4. O fatiamento também funciona, como uma lista. Ao fatiar uma lista, você obtém uma nova lista; quando você corta uma tupla, obtém uma nova tupla.

A principal diferença entre tuplas e listas é que as tuplas não podem ser alteradas. Em termos técnicos, as tuplas são imutáveis. Em termos práticos, eles não têm métodos que permitam alterá-los. Listas têm métodos como append(), extend(), insert(), remove(), e pop(). As tuplas não possuem nenhum desses métodos. Você pode fatiar uma tupla (porque isso cria uma nova tupla) e pode verificar se uma tupla contém um valor específico (porque isso não muda a tupla) e... é sobre isso.

# continuação do exemplo anterior
>>> a_tuple
('a', 'b', 'mpilgrim', 'z', 'example')
>>> a_tuple.append("new")               ①
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'append'
>>> a_tuple.remove("z")                 ②
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'remove'
>>> a_tuple.index("example")            ③
4
>>> "z" in a_tuple                      ④
True
  1. Você não pode adicionar elementos a uma tupla. Tuples não têm método append() ou extend().
  2. Você não pode remover elementos de uma tupla. Tuples não têm método remove() ou pop().
  3. Você pode encontrar elementos em uma tupla, pois isso não altera a tupla.
  4. Você também pode usar o operador in para verificar se um elemento existe na tupla.

Então, para que servem as tuplas?

  • Tuplas são mais rápidas do que listas. Se você está definindo um set constante de valores e tudo o que vai fazer com ele é iterar por ele, use uma tupla em vez de uma lista.
  • Isso torna seu código mais seguro se você “protege contra gravação” os dados que não precisam ser alterados. Usar uma tupla em vez de uma lista é como ter uma declaração assert implícita que mostra que esses dados são constantes e que um pensamento especial (e uma função específica) é necessário para substituí-lo.
  • Algumas tuplas podem ser usadas como chaves de dicionário (especificamente, tuplas que contêm valores imutáveis como strings, números e outras tuplas). As listas nunca podem ser usadas como chaves de dicionário, porque as listas não são imutáveis.

Observação

Tuplas podem ser convertidas em listas e vice-versa. A função tuple() embutida pega uma lista e retorna uma tupla com os mesmos elementos, e a função list() pega uma tupla e retorna uma lista. Na verdade, tuple() congela uma lista e list() descongela uma tupla.

Tuplas em um contexto booleano

Você pode usar tuplas em um contexto booleano, como uma instrução if.

>>> def eh_verdadeiro(qualquer_coisa):
...   if qualquer_coisa:
...     print("sim, é verdadeiro")
...   else:
...     print("não, é verdadeiro")
...
>>> eh_verdadeiro(())             ①
não, é verdadeiro
>>> eh_verdadeiro(('a', 'b'))     ②
sim, é verdadeiro
>>> eh_verdadeiro((False,))       ③
sim, é verdadeiro
>>> type((False))                 ④
<class 'bool'>
>>> type((False,))
<class 'tuple'>
  1. Em um contexto booleano, uma tupla vazia é falsa.
  2. Qualquer tupla com pelo menos um item é verdadeira.
  3. Qualquer tupla com pelo menos um item é verdadeira. O valor dos itens é irrelevante. Mas o que aquela vírgula está fazendo aí?
  4. Para criar uma tupla de um item, você precisa de uma vírgula após o valor. Sem a vírgula, o Python apenas assume que você tem um par extra de parênteses, o que é inofensivo, mas não cria uma tupla.

Atribuição de vários valores de uma vez

Aqui está um atalho de programação legal: no Python, você pode usar uma tupla para atribuir vários valores de uma vez.

>>> v = ('a', 2, True)
>>> (x, y, z) = v       ①
>>> x
'a'
>>> y
2
>>> z
True
  1. v é uma tupla de três elementos e (x, y, z) é uma tupla de três variáveis. Atribuir um ao outro atribui cada um dos valores de v a cada uma das variáveis, em ordem.

Isso tem todos os tipos de uso. Suponha que você queira atribuir nomes a um intervalo de valores. Você pode usar a função range() incorporada com atribuição de várias variáveis ​​para atribuir rapidamente valores consecutivos.

>>> (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)  ①
>>> MONDAY                                                                       ②
0
>>> TUESDAY
1
>>> SUNDAY
6
  1. A função range() embutida constrói uma sequência de inteiros. (Tecnicamente, a função range() retorna um iterador, não uma lista ou tupla, mas você aprenderá sobre essa distinção mais tarde.) MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, e SUNDAY são as variáveis ​​que você está definindo. (Este exemplo veio do módulo calendar, um pequeno módulo divertido que imprime calendários, como o programa UNIX cal. O módulo calendar define constantes inteiras para os dias da semana.)
  2. Agora, cada variável tem seu valor: MONDAY é 0, TUESDAY é 1 e assim por diante.

Você também pode usar a atribuição de várias variáveis para criar funções que retornam vários valores, simplesmente retornando uma tupla de todos os valores. O chamador pode tratá-lo como uma única tupla ou pode atribuir os valores a variáveis individuais. Muitas bibliotecas Python padrão fazem isso, incluindo o módulo os, sobre o qual você aprenderá no próximo capítulo.

Conjuntos (sets)

Um set é uma “bolsa” não ordenada de valores únicos. Um único set pode conter valores de qualquer tipo de dados imutável. Depois de ter dois sets, você pode fazer operações de set padrão, como união, interseção e diferença de set.

Criando um Conjunto

Primeiras coisas primeiro. Criar um set é fácil.

>>> um_conjunto = {1}     ①
>>> um_conjunto
{1}
>>> type(um_conjunto)     ②
<class 'set'>
>>> um_conjunto = {1, 2}  ③
>>> um_conjunto
{1, 2}
  1. Para criar um set com um valor, coloque o valor entre chaves ({}).
  2. Os sets são realmente implementados como classes, mas não se preocupe com isso por enquanto.
  3. Para criar um set com vários valores, separe os valores com vírgulas e envolva tudo com chaves.

Você também pode criar um set de uma lista.

>>> uma_lista = ['a', 'b', 'mpilgrim', True, False, 42]
>>> um_conjunto = set(uma_lista)                     ①
>>> um_conjunto                                      ②
{'a', False, 'b', True, 'mpilgrim', 42}
>>> uma_lista                                        ③
['a', 'b', 'mpilgrim', True, False, 42]
  1. Para criar um set a partir de uma lista, use a set()função. (Pedantes que sabem sobre como os sets são implementados irão apontar que isso não é realmente chamar uma função, mas instanciar uma classe. Eu prometo que você aprenderá a diferença mais tarde neste livro. Por enquanto, apenas saiba que isso set()atua como uma função, e ele retorna um set.)
  2. Como mencionei anteriormente, um único set pode conter valores de qualquer tipo de dados. E, como mencionei anteriormente, os sets não são ordenados . Este set não lembra a ordem original da lista que foi usada para criá-lo. Se você fosse adicionar itens a este set, ele não se lembraria da ordem em que você os adicionou.
  3. A lista original não foi alterada.

Ainda não tem valores? Não é um problema. Você pode criar um set vazio.

>>> um_conjunto = set()    ①
>>> um_conjunto            ②
set()
>>> type(um_conjunto)      ③
<class 'set'>
>>> len(um_conjunto)       ④
0
>>> not_sure = {}          ⑤
>>> type(not_sure)
<class 'dict'>
  1. Para criar um set vazio, chame set() sem argumentos.
  2. A representação impressa de um set vazio parece um pouco estranha. Você estava esperando {}, talvez? Isso denotaria um dicionário vazio, não um set vazio. Você aprenderá sobre dicionários posteriormente neste capítulo.
  3. Apesar da estranha representação impressa, este é um set…
  4. …E este set não tem membros.
  5. Devido a peculiaridades históricas transportadas do Python 2, você não pode criar um set vazio com duas chaves. Na verdade, isso cria um dicionário vazio, não um set vazio.

Modificando um set (Conjunto)

Existem duas maneiras diferentes de adicionar valores a um set existente: o método add() e o método update().

>>> um_conjunto = {1, 2}
>>> um_conjunto.add(4)  ①
>>> um_conjunto
{1, 2, 4}
>>> len(um_conjunto)    ②
3
>>> um_conjunto.add(1)  ③
>>> um_conjunto
{1, 2, 4}
>>> len(um_conjunto)    ④
3
  1. O método add() usa um único argumento, que pode ser qualquer tipo de dados, e adiciona o valor fornecido ao set.
  2. Este set agora tem 3 membros.
  3. Sets são bolsas de valores únicos. Se você tentar adicionar um valor que já existe no set, não fará nada. Não gerará um erro; é apenas um ambiente autônomo.
  4. Este set ainda tem 3 membros.
>>> um_conjunto = {1, 2, 3}
>>> um_conjunto
{1, 2, 3}
>>> um_conjunto.update({2, 4, 6})                       ①
>>> um_conjunto                                         ②
{1, 2, 3, 4, 6}
>>> um_conjunto.update({3, 6, 9}, {1, 2, 3, 5, 8, 13})  ③
>>> um_conjunto
{1, 2, 3, 4, 5, 6, 8, 9, 13}
>>> um_conjunto.update([10, 20, 30])                    ④
>>> um_conjunto
{1, 2, 3, 4, 5, 6, 8, 9, 10, 13, 20, 30}
  1. O método update() pega um argumento, um set, e adiciona todos os seus membros ao set original. É como se você chamasse o método add() com cada membro do set.
  2. Valores duplicados são ignorados, pois os sets não podem conter duplicatas.
  3. Você pode realmente chamar o método update() com qualquer número de argumentos. Quando chamado com dois sets, o método update() adiciona todos os membros de cada set ao set original (eliminando duplicatas).
  4. O método update() pode pegar objetos de vários tipos de dados diferentes, incluindo listas. Quando chamado com uma lista, o método update() adiciona todos os itens da lista ao set original.

Removendo itens de um set

Existem três maneiras de remover valores individuais de um set. Os dois primeiros, discard() e remove(), têm uma diferença sutil.

>>> um_conjunto = {1, 3, 6, 10, 15, 21, 28, 36, 45}
>>> um_conjunto
{1, 3, 36, 6, 10, 45, 15, 21, 28}
>>> um_conjunto.discard(10)                        ①
>>> um_conjunto
{1, 3, 36, 6, 45, 15, 21, 28}
>>> um_conjunto.discard(10)                        ②
>>> um_conjunto
{1, 3, 36, 6, 45, 15, 21, 28}
>>> um_conjunto.remove(21)                         ③
>>> um_conjunto
{1, 3, 36, 6, 45, 15, 28}
>>> um_conjunto.remove(21)                         ④
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 21
  1. O método discard() usa um único valor como argumento e remove esse valor do set.
  2. Se você chamar o método discard() com um valor que não existe no set, ele não fará nada. Sem erro; é apenas um ambiente autônomo.
  3. O método remove() também aceita um único valor como argumento e também remove esse valor do set.
  4. Aqui está a diferença: se o valor não existir no set, o método remove() gerará uma exceção KeyError.

Como as listas, os sets têm um método pop().

>>> um_conjunto = {1, 3, 6, 10, 15, 21, 28, 36, 45}
>>> um_conjunto.pop()                                ①
1
>>> um_conjunto.pop()
3
>>> um_conjunto.pop()
36
>>> um_conjunto
{6, 10, 45, 15, 21, 28}
>>> um_conjunto.clear()                              ②
>>> um_conjunto
set()
>>> um_conjunto.pop()                                ③
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'pop from an empty set'
  1. O método pop() remove um único valor de um set e retorna o valor. No entanto, como os sets não são ordenados, não há "último" valor em um set, portanto, não há como controlar qual valor é removido. É completamente arbitrário.
  2. O método clear() remove todos os valores de um set, deixando você com um set vazio. Isso é equivalente a um_conjunto = set(), o que criaria um novo set vazio e sobrescreveria o valor anterior da variável um_conjunto.
  3. A tentativa de retirar um valor de um set vazio levantará uma exceção KeyError.

Operações comuns com set (conjuntos)

O tipo set do Python oferece suporte a várias operações comuns com conjuntos.

>>> um_conjunto = {2, 4, 5, 9, 12, 21, 30, 51, 76, 127, 195}
>>> 30 in um_conjunto                                                     ①
True
>>> 31 in um_conjunto
False
>>> b_set = {1, 2, 3, 5, 6, 8, 9, 12, 15, 17, 18, 21}
>>> um_conjunto.union(b_set)                                              ②
{1, 2, 195, 4, 5, 6, 8, 12, 76, 15, 17, 18, 3, 21, 30, 51, 9, 127}
>>> um_conjunto.intersection(b_set)                                       ③
{9, 2, 12, 5, 21}
>>> um_conjunto.difference(b_set)                                         ④
{195, 4, 76, 51, 30, 127}
>>> um_conjunto.symmetric_difference(b_set)                               ⑤
{1, 3, 4, 6, 8, 76, 15, 17, 18, 195, 127, 30, 51}
  1. Para testar se um valor é membro de um set, use o operador in. Isso funciona da mesma forma que listas.
  2. O método union() retorna um novo set contendo todos os elementos que estão em qualquer um dos sets.
  3. O método intersection() retorna um novo set contendo todos os elementos que estão em ambos os sets.
  4. O método difference() retorna um novo set contendo todos os elementos que estão em um_conjunto, mas não em b_set.
  5. O método symmetric_difference() retorna um novo set contendo todos os elementos que estão em exatamente um dos sets.

Três desses métodos são simétricos.

# continuação do exemplo anterior
>>> b_set.symmetric_difference(um_conjunto)                                             ①
{3, 1, 195, 4, 6, 8, 76, 15, 17, 18, 51, 30, 127}
>>> b_set.symmetric_difference(um_conjunto) == um_conjunto.symmetric_difference(b_set)  ②
True
>>> b_set.union(um_conjunto) == um_conjunto.union(b_set)                                ③
True
>>> b_set.intersection(um_conjunto) == um_conjunto.intersection(b_set)                  ④
True
>>> b_set.difference(um_conjunto) == um_conjunto.difference(b_set)                      ⑤
False
  1. A diferença simétrica de um_conjunto de b_set parece diferente da diferença simétrica de b_set de um_conjunto, mas lembre-se, os sets não são ordenados. Quaisquer dois sets que contenham todos os mesmos valores (sem nenhum restante) são considerados iguais.
  2. E é exatamente isso que acontece aqui. Não se deixe enganar pela representação impressa do Python Shell desses sets. Eles contêm os mesmos valores, portanto, são iguais.
  3. A união de dois sets também é simétrica.
  4. A interseção de dois sets também é simétrica.
  5. A diferença de dois sets não é simétrica. Isso faz sentido; é análogo a subtrair um número de outro. A ordem dos operandos é importante.

Finalmente, existem algumas perguntas que você pode fazer aos sets.

>>> um_conjunto = {1, 2, 3}
>>> b_set = {1, 2, 3, 4}
>>> um_conjunto.issubset(b_set)    ①
True
>>> b_set.issuperset(um_conjunto)  ②
True
>>> um_conjunto.add(5)             ③
>>> um_conjunto.issubset(b_set)
False
>>> b_set.issuperset(um_conjunto)
False
  1. um_conjunto é um subconjunto de b_set  - todos os membros de um_conjunto também são membros de b_set.
  2. Fazendo a mesma pergunta ao contrário, b_set é um superconjunto de um_conjunto, porque todos os membros de um_conjunto também são membros de b_set.
  3. Assim que você adiciona um valor a um_conjunto que não está em b_set, ambos os testes retornam False.

Conjuntos em um contexto booleano

Você pode usar sets em um contexto booleano, como uma instrução if.

>>> def eh_verdadeiro(qualquer_coisa):
...   if qualquer_coisa:
...     print("sim, é verdadeiro")
...   else:
...     print("não, é verdadeiro")
...
>>> eh_verdadeiro(set())          ①
não, é verdadeiro
>>> eh_verdadeiro({'a'})          ②
sim, é verdadeiro
>>> eh_verdadeiro({False})        ③
sim, é verdadeiro
  1. Em um contexto booleano, um set vazio é falso.
  2. Qualquer set com pelo menos um item é verdadeiro.
  3. Qualquer set com pelo menos um item é verdadeiro. O valor dos itens é irrelevante.

Dicionários

Um dicionário é um set não ordenado de pares chave-valor. Ao adicionar uma chave a um dicionário, você também deve adicionar um valor para essa chave. (Você sempre pode alterar o valor posteriormente). Os dicionários Python são otimizados para recuperar o valor quando você conhece a chave, mas não o contrário.

Observação

Um dicionário em Python é como um hash em Perl 5. Em Perl 5, as variáveis que armazenam hashes sempre começam com um caractere %. No Python, as variáveis podem ter qualquer nome, e o Python rastreia o tipo de dados internamente.

Criando um Dicionário

Criar um dicionário é fácil. A sintaxe é semelhante a criação de sets, mas em vez de valores, você tem pares de chave-valores. Depois de ter um dicionário, você pode pesquisar os valores por sua chave.

>>> um_dicionario = {'server': 'db.diveintopython3.org', 'database': 'mysql'}  ①
>>> um_dicionario
{'server': 'db.diveintopython3.org', 'database': 'mysql'}
>>> um_dicionario['server']                                                    ②
'db.diveintopython3.org'
>>> um_dicionario['database']                                                  ③
'mysql'
>>> um_dicionario['db.diveintopython3.org']                                    ④
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'db.diveintopython3.org'
  1. Primeiro, você cria um novo dicionário com dois itens e o atribui à variável um_dicionario. Cada item é um par de chave-valores e todo o conjunto de itens está entre chaves.
  2. 'server' é uma chave e seu valor associado, referenciado por um_dicionario['server'], é 'db.diveintopython3.org'.
  3. 'database' é uma chave e seu valor associado, referenciado por um_dicionario['database'], é 'mysql'.
  4. Você pode obter valores por chave, mas não pode obter chaves por valor. Então um_dicionario['server'] é 'db.diveintopython3.org', mas um_dicionario['db.diveintopython3.org'] levanta uma exceção, porque 'db.diveintopython3.org' não é uma chave.

Modificando um Dicionário

Os dicionários não têm nenhum limite de tamanho predefinido. Você pode adicionar novos pares de chave-valores a um dicionário a qualquer momento ou pode modificar o valor de uma chave existente. Continuando com o exemplo anterior:

>>> um_dicionario
{'server': 'db.diveintopython3.org', 'database': 'mysql'}
>>> um_dicionario['database'] = 'blog'  ①
>>> um_dicionario
{'server': 'db.diveintopython3.org', 'database': 'blog'}
>>> um_dicionario['user'] = 'mark'      ②
>>> um_dicionario                       ③
{'server': 'db.diveintopython3.org', 'user': 'mark', 'database': 'blog'}
>>> um_dicionario['user'] = 'dora'      ④
>>> um_dicionario
{'server': 'db.diveintopython3.org', 'user': 'dora', 'database': 'blog'}
>>> um_dicionario['User'] = 'mark'      ⑤
>>> um_dicionario
{'User': 'mark', 'server': 'db.diveintopython3.org', 'user': 'dora', 'database': 'blog'}
  1. Você não pode ter chaves duplicadas em um dicionário. Atribuir um valor a uma chave existente apagará o valor antigo.
  2. Você pode adicionar novos pares de chave-valores a qualquer momento. Essa sintaxe é idêntica à modificação de valores existentes.
  3. O novo item do dicionário (chave 'user', valor 'mark') parece estar no meio. Na verdade, foi apenas uma coincidência que os itens parecessem estar em ordem no primeiro exemplo; é uma coincidência que eles pareçam estar fora de ordem agora.
  4. Atribuir um valor a uma chave de dicionário existente simplesmente substitui o valor antigo pelo novo.
  5. Isso mudará o valor da chave user de volta para "mark"? Não! Observe a chave de perto - é uma capital U em "User". As chaves de dicionário diferenciam maiúsculas de minúsculas, portanto, esta instrução está criando um novo par chave-valor, não substituindo um existente. Pode ser parecido com você, mas no que diz respeito ao Python, é completamente diferente.

Dicionários de valores mistos

Dicionários não são apenas para strings. Os valores do dicionário podem ser qualquer tipo de dados, incluindo inteiros, booleanos, objetos arbitrários ou até mesmo outros dicionários. E dentro de um único dicionário, os valores não precisam ser todos do mesmo tipo; você pode misturar e combinar conforme necessário. As chaves de dicionário são mais restritas, mas podem ser strings, inteiros e alguns outros tipos. Você também pode misturar e combinar os principais tipos de dados em um dicionário.

Na verdade, você já viu um dicionário com chaves e valores que não sejam strings, em seu primeiro programa Python.

SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
            1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}

Vamos separar isso no shell interativo.

>>> SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
...             1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}
>>> len(SUFFIXES)      ①
2
>>> 1000 in SUFFIXES   ②
True
>>> SUFFIXES[1000]     ③
['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
>>> SUFFIXES[1024]     ④
['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
>>> SUFFIXES[1000][3]  ⑤
'TB'
  1. Como listas e sets, a função len() fornece o número de chaves em um dicionário.
  2. E como listas e sets, você pode usar o operador in para testar se uma chave específica está definida em um dicionário.
  3. 1000 é uma chave no dicionário SUFFIXES; seu valor é uma lista de oito itens (oito strings, para ser mais preciso).
  4. Da mesma forma, 1024 é uma chave no dicionário SUFFIXES; seu valor também é uma lista de oito itens.
  5. Como SUFFIXES[1000] é uma lista, você pode endereçar itens individuais na lista por seu índice baseado em 0.

Dicionários em um contexto booleano

Dicionários vazios são falsos; todos os outros dicionários são verdadeiros.

Você também pode usar um dicionário em um contexto booleano, como uma instrução if.

>>> def eh_verdadeiro(qualquer_coisa):
...   if qualquer_coisa:
...     print("sim, é verdadeiro")
...   else:
...     print("não, é verdadeiro")
...
>>> eh_verdadeiro({})             ①
não, é verdadeiro
>>> eh_verdadeiro({'a': 1})       ②
sim, é verdadeiro
  1. Em um contexto booleano, um dicionário vazio é falso.
  2. Qualquer dicionário com pelo menos um par de chave-valores é verdadeiro.

None

None é uma constante especial em Python. É um valor nulo. None não é o mesmo que False. Nonenão é 0. None não é uma string vazia. Comparando None com qualquer coisa diferente de None sempre retornará False.

None é o único valor nulo. Ele tem seu próprio tipo de dados (NoneType). Você pode atribuir None a qualquer variável, mas não pode criar outros objetos NoneType. Todas as variáveis cujo valor é None são iguais entre si.

>>> type(None)
<class 'NoneType'>
>>> None == False
False
>>> None == 0
False
>>> None == ''
False
>>> None == None
True
>>> x = None
>>> x == None
True
>>> y = None
>>> x == y
True

None num contexto booleano

Em um contexto booleano, None é falso e not None é verdadeiro.

>>> def eh_verdadeiro(qualquer_coisa):
...   if qualquer_coisa:
...     print("sim, é verdadeiro")
...   else:
...     print("não, é verdadeiro")
...
>>> eh_verdadeiro(None)
não, é verdadeiro
>>> eh_verdadeiro(not None)
sim, é verdadeiro

Leitura Adicional

Esse artigo é uma tradução de um capítulo do livro "Dive Into Python 3" escrito por Mark Pilgrim. Você pode ler o livro desde o início em português clicando aqui.

Traduzido por Acervo Lima. O original pode ser acessado aqui.

Licença

sexta-feira, 7 de maio de 2021

Seu primeiro programa Python

❝Não enterre seu fardo em silêncio santo. Você tem um problema? Excelente. Alegre-se, mergulhe e investigue.❞
- Ven. Henepola Gunaratana

Mergulho

A convenção dita que devo aborrecê-lo com os blocos de construção fundamentais da programação, para que possamos trabalhar lentamente para construir algo útil. Vamos pular tudo isso. Aqui está um programa Python completo e funcional. Provavelmente não faz absolutamente nenhum sentido para você. Não se preocupe com isso, porque você vai dissecar linha por linha. Mas leia primeiro e veja o que você pode fazer com isso, se é que pode fazer alguma coisa.

SUFIXOS = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
           1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}

def tamanho_aproximado(tamanho, um_kilobyte_eh_1024_bytes=True):
    '''Converta um tamanho de arquivo em um formato legível.

    Argumentos keyword:
    tamanho -- tamanho do arquivo em bytes
    um_kilobyte_eh_1024_bytes -- if True (padrão), usa multiplos de 1024
                            if False, usa multiplos de 1000

    Retorna: string

    '''
    if tamanho < 0:
        raise ValueError('O número não deve ser negativo')

    multiplo = 1024 if um_kilobyte_eh_1024_bytes else 1000
    for sufixo in SUFIXOS[multiplo]:
        tamanho /= multiplo
        if tamanho < multiplo:
            return '{0:.1f} {1}'.format(tamanho, sufixo)

    raise ValueError('numero muito longo')

if __name__ == '__main__':
    print(tamanho_aproximado(1000000000000, False))
    print(tamanho_aproximado(1000000000000))

Agora vamos executar este programa na linha de comando. No Windows, será mais ou menos assim:

PS C:\Users\user\working\dive_into_python_3> & C:/Users/user/AppData/Local/Programs/Python/Python37/python.exe c:/Users/user/working/dive_into_python_3/teste.py 
1.0 TB
931.3 GiB

No Mac OS X ou Linux, seria algo assim:

you@localhost:~/diveintopython3/examples$ python3 teste.py
1.0 TB
931.3 GiB

O que acabou de acontecer? Você executou seu primeiro programa Python. Você chamou o interpretador Python na linha de comando e passou o nome do script que queria que o Python executasse. O script define uma única função, a função tamanho_aproximado(), que pega um tamanho de arquivo exato em bytes e calcula um tamanho “bonito” (mas aproximado). (Você provavelmente já viu isso no Windows Explorer, ou no Mac OS X Finder, ou Nautilus ou Dolphin ou Thunar no Linux. Se você exibir uma pasta de documentos como uma lista de várias colunas, será exibida uma tabela com o ícone do documento, o nome do documento, o tamanho, o tipo, a data da última modificação e assim por diante. Se a pasta contiver um arquivo de 1093 bytes denominado TODO, seu gerenciador de arquivos não será exibido TODO 1093 bytes; em vez disso, aparecerá algo como TODO 1 KB. Isso é o que a função tamanho_aproximado() faz).

Observe a parte inferior do script e você verá duas chamadas para print(tamanho_aproximado(arguments)). Essas são chamadas de função - primeiro chamando a função tamanho_aproximado() e passando uma série de argumentos, depois pegando o valor de retorno e passando-o direto para a função print(). A função print() é integrada; você nunca verá uma declaração explícita disso. Você pode simplesmente usá-la, a qualquer hora, em qualquer lugar. (Existem muitas funções integradas e muito mais funções que são separadas em módulos. Paciência, gafanhoto).

Então, por que a execução do script na linha de comando fornece sempre a mesma saída? Nós vamos chegar a isso. Primeiro, vamos examinar essa função tamanho_aproximado().

Declarando funções

Python tem funções como a maioria das outras linguagens, mas não tem arquivos de cabeçalho separados como C++ ou seções interface/implementation como Pascal. Quando você precisar de uma função, basta declará-la, assim:

def tamanho_aproximado(tamanho, um_kilobyte_eh_1024_bytes=True):

A palavra-chave def inicia a declaração da função, seguida pelo nome da função, seguido pelos argumentos entre parênteses. Vários argumentos são separados por vírgulas.

Observe também que a função não define um tipo de dados de retorno. As funções Python não especificam o tipo de dados de seu valor de retorno; eles nem mesmo especificam se retornam ou não um valor. (Na verdade, toda função Python retorna um valor; se a função alguma vez executar uma instrução return, ela retornará esse valor; caso contrário, retornará None o valor nulo do Python).

Observação

Em algumas linguagens, as funções (que retornam um valor) começam com function, e as sub-rotinas (que não retornam um valor) começam com sub. Não há sub-rotinas em Python. Tudo é uma função, todas as funções retornam um valor (mesmo que seja None) e todas as funções começam com def.

A função tamanho_aproximado() leva os dois argumentos -  tamanho e um_kilobyte_eh_1024_bytes - mas nenhum dos argumentos especifica um tipo de dados. Em Python, as variáveis nunca são tipadas explicitamente. Python descobre que tipo é uma variável e mantém o controle dela internamente.

Observação

Em Java e outras linguagens tipadas estaticamente, você deve especificar o tipo de dados do valor de retorno da função e cada argumento da função. Em Python, você nunca especifica explicitamente o tipo de dados de nada. Com base no valor que você atribui, o Python rastreia o tipo de dados internamente.

Argumentos opcionais e nomeados

Python permite que argumentos de função tenham valores padrão; se a função for chamada sem o argumento, o argumento obterá seu valor padrão. Além disso, os argumentos podem ser especificados em qualquer ordem usando argumentos nomeados.

Vamos dar outra olhada na declaração da função tamanho_aproximado():

def tamanho_aproximado(tamanho, um_kilobyte_eh_1024_bytes=True):

O segundo argumento, um_kilobyte_eh_1024_bytes, especifica um valor padrão igual a True. Isso significa que o argumento é opcional; você pode chamar a função sem ele e o Python agirá como se você tivesse chamado como a função passando o valor True para o segundo parâmetro.

Agora observe a parte inferior do script:

if __name__ == '__main__':
    print(tamanho_aproximado(1000000000000, False))   ①
    print(tamanho_aproximado(1000000000000))          ②
  1. Isso chama a função tamanho_aproximado() com dois argumentos. Dentro da função tamanho_aproximado(), um_kilobyte_eh_1024_bytes será False, já que você passou explicitamente False como o segundo argumento.

  2. Isso chama a função tamanho_aproximado() com apenas um argumento. Mas tudo bem, porque o segundo argumento é opcional! Como o chamador não especifica, o segundo argumento é padronizado (True), conforme definido pela declaração da função.

Você também pode passar valores para uma função por nome.

Observação

teste é o nome do nosso script python. Execute o shell python na pasta onde o arquivo teste.py foi salvo.

Por exemplo: No PowerShell (windows), terminal (Linux, Mac) execute esses comando: cd caminho_da_pasta.
Exemplo PowerShell: cd C:\Users\dive_into_python_3; No terminal: cd /home/user/dive_into_python_3.

>>> from teste import tamanho_aproximado
>>> tamanho_aproximado(4000, um_kilobyte_eh_1024_bytes=False)           ①
'4.0 KB'
>>> tamanho_aproximado(tamanho=4000, um_kilobyte_eh_1024_bytes=False)   ②
'4.0 KB'
>>> tamanho_aproximado(um_kilobyte_eh_1024_bytes=False, tamanho=4000)   ③
'4.0 KB'
>>> tamanho_aproximado(um_kilobyte_eh_1024_bytes=False, 4000)           ④
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument
>>> tamanho_aproximado(tamanho=4000, False)                             ⑤
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument
  1. Isso chama a função tamanho_aproximado() passando o valor 4000 para o primeiro argumento (tamanho) e False para o argumento denominado um_kilobyte_eh_1024_bytes. (Esse é o segundo argumento, mas não importa, como você verá em um minuto).

  2. Isso chama a funçãotamanho_aproximado() passando o valor 4000 para o argumento denominado tamanho e False para o argumento denominado um_kilobyte_eh_1024_bytes. (Esses argumentos nomeados estão na mesma ordem que os argumentos listados na declaração da função, mas isso também não importa).

  3. Isso chama a função tamanho_aproximado() passando o valor False para o argumento denominado um_kilobyte_eh_1024_bytes e 4000 para o argumento denominado tamanho. (Viu? Eu disse que a ordem não importava).

  4. Essa chamada falha, porque você tem um argumento nomeado seguido por um argumento não nomeado (posicional) e isso nunca funciona. Lendo a lista de argumentos da esquerda para a direita, uma vez que você tenha um único argumento nomeado, o restante dos argumentos também deve ser nomeado.

  5. Esta chamada também falha, pelo mesmo motivo da chamada anterior. Isso é surpreendente? Afinal, você passou 4000 para o argumento nomeado tamanho, então “obviamente” esse valor False era para o argumento um_kilobyte_eh_1024_bytes. Mas Python não funciona assim. Assim que você tiver um argumento nomeado, todos os argumentos à direita dele também precisam ser argumentos nomeados.

Escrevendo código legível

Não vou aborrecê-lo com um longo discurso de abanar o dedo sobre a importância de documentar seu código. Saiba que o código é escrito uma vez, mas lido muitas vezes, e o público mais importante para o seu código é você mesmo, seis meses depois de escrevê-lo (ou seja, depois que você esqueceu tudo, mas precisa consertar algo). Python facilita a escrita de código legível, então tire proveito disso. Você vai me agradecer em seis meses.

Strings de documentação

Você pode documentar uma função Python fornecendo a ela uma string de documentação (docstring para abreviar). Neste programa, a função tamanho_aproximado() possui uma docstring:

def tamanho_aproximado(tamanho, um_kilobyte_eh_1024_bytes=True):
    '''Converta um tamanho de arquivo em um formato legível.

    Argumentos keyword:
    tamanho -- tamanho do arquivo em bytes
    um_kilobyte_eh_1024_bytes -- if True (padrão), usa multiplos de 1024
                                 if False, usa multiplos de 1000

    Retorna: string

    '''

Cada função merece uma docstring decente.

As aspas triplas significam uma string de várias linhas. Tudo entre as aspas iniciais e finais faz parte de uma única string, incluindo retornos de carro, espaço em branco inicial e outros caracteres de aspas. Você pode usá-los em qualquer lugar, mas verá que são usados ​​com mais frequência ao definir uma docstring.

Observação

As aspas triplas também são uma maneira fácil de definir uma string com aspas simples e duplas, como qq/.../ no Perl 5.

Tudo o que está entre as aspas triplas é da docstring da função, que documenta o que a função faz. A docstring, se existir, deve ser a primeira coisa definida em uma função (ou seja, na próxima linha após a declaração da função). Tecnicamente, você não precisa atribuir um valor à sua docstring da função, mas sempre deve. Eu sei que você já ouviu isso em todas as aulas de programação que já fez, mas o Python oferece um incentivo adicional: A docstring está disponível em tempo de execução como um atributo da função.

Observação

Muitas IDEs Python usam a docstring para fornecer documentação sensível ao contexto, de forma que quando você digita o nome de uma função, a docstring aparece como uma dica de ferramenta. Isso pode ser extremamente útil, mas é tão bom quanto a docstring que você escreve.

O Caminho de Pesquisa import

Antes de prosseguir, quero mencionar brevemente o caminho de pesquisa da biblioteca. Python procura em vários lugares quando você tenta importar um módulo. Especificamente, ele procura em todos os diretórios definidos em sys.path. Esta é apenas uma lista e você pode facilmente visualizá-la ou modificá-la com métodos de lista padrão. (Você aprenderá mais sobre listas em tipos de dados nativos).

>>> import sys                                                 ①
>>> sys.path                                                   ②
['', 
 '/usr/lib/python31.zip', 
 '/usr/lib/python3.1',
 '/usr/lib/python3.1/plat-linux2@EXTRAMACHDEPPATH@', 
 '/usr/lib/python3.1/lib-dynload', 
 '/usr/lib/python3.1/dist-packages', 
 '/usr/local/lib/python3.1/dist-packages']
>>> sys                                                        ③
<module 'sys' (built-in)>
>>> sys.path.insert(0, '/home/mark/diveintopython3/examples')  ④
>>> sys.path                                                   ⑤
['/home/mark/diveintopython3/examples', 
 '', 
 '/usr/lib/python31.zip', 
 '/usr/lib/python3.1', 
 '/usr/lib/python3.1/plat-linux2@EXTRAMACHDEPPATH@', 
 '/usr/lib/python3.1/lib-dynload', 
 '/usr/lib/python3.1/dist-packages', 
 '/usr/local/lib/python3.1/dist-packages']
  1. A importação do módulo sys disponibiliza todas as suas funções e atributos.

  2. sys.path é uma lista de nomes de diretório que constituem o caminho de pesquisa atual. (O seu terá uma aparência diferente, dependendo do seu sistema operacional, da versão do Python que você está executando e de onde foi instalado originalmente). O Python procurará nesses diretórios (nesta ordem) um arquivo .py cujo nome corresponda ao que você está tentando importar.

  3. Na verdade, eu menti; a verdade é mais complicada do que isso, porque nem todos os módulos são armazenados como arquivos .py. Alguns são módulos integrados; eles são realmente embutidos no próprio Python. Módulos integrados se comportam como módulos regulares, mas seu código-fonte Python não está disponível, porque eles não são escritos em Python! (Como o próprio Python, esses módulos integrados são escritos em C).
  4. Você pode adicionar um novo diretório ao caminho de pesquisa do Python em tempo de execução, adicionando o nome do diretório a sys.path, e o Python também procurará nesse diretório, sempre que você tentar importar um módulo. O efeito dura enquanto o Python estiver em execução.

  5. Ao usar sys.path.insert(0, new_path), você inseriu um novo diretório como o primeiro item da lista sys.path e, portanto, no início do caminho de pesquisa do Python. Quase sempre é isso que você deseja. Em caso de conflitos de nomenclatura (por exemplo, se Python vem com a versão 2 de uma biblioteca específica, mas você deseja usar a versão 3), isso garante que seus módulos serão encontrados e usados ​​em vez dos módulos que vieram com Python.

Tudo é um objeto

Caso você tenha perdido, eu acabei de dizer que as funções do Python têm atributos e que esses atributos estão disponíveis em tempo de execução. Uma função, como tudo o mais em Python, é um objeto.

Execute o shell Python interativo e siga em frente:

>>> import teste                                   ①
>>> print(teste.tamanho_aproximado(4096, True))    ②
4.0 KiB
>>> print(teste.tamanho_aproximado.__doc__)        ③
Converta um tamanho de arquivo em um formato legível.

    Argumentos keyword:
    tamanho -- tamanho do arquivo em bytes
    um_kilobyte_eh_1024_bytes -- if True (padrão), usa multiplos de 1024
                                 if False, usa multiplos de 1000

    Retorna: string
  1. A primeira linha importa o programa teste como um módulo - um pedaço de código que você pode usar interativamente ou de um programa Python maior. Depois de importar um módulo, você pode fazer referência a qualquer uma de suas funções, classes ou atributos públicos. Os módulos podem fazer isso para acessar a funcionalidade em outros módulos, e você também pode fazer isso no shell interativo do Python. Este é um conceito importante, e você verá muito mais dele ao longo deste livro.

  2. Quando você deseja usar funções definidas em módulos importados, você precisa incluir o nome do módulo. Então você não pode simplesmente dizer tamanho_aproximado; deve ser teste.tamanho_aproximado. Se você usou classes em Java, isso deve parecer vagamente familiar.

  3. Em vez de chamar a função como você esperava, solicitou um dos atributos da função, __doc__.

Observação

import em Python é como require em Perl. Depois de criar um módulo Python, você acessa suas funções com module.function; uma vez que você precisa de um módulo Perl, você acessa suas funções com module::function.

O que é um objeto?

Tudo em Python é um objeto e tudo pode ter atributos e métodos. Todas as funções têm um atributo __doc__ embutido, que retorna a docstring definida no código-fonte da função. O módulo sys é um objeto que possui (entre outras coisas) um atributo denominado path. E assim por diante.

Ainda assim, isso não responde à questão mais fundamental: o que é um objeto? Diferentes linguagens de programação definem “objeto” de maneiras diferentes. Em alguns, significa que todos os objetos devem ter atributos e métodos; em outros, significa que todos os objetos são subclassíveis. Em Python, a definição é mais ampla. Alguns objetos não têm atributos nem métodos, mas poderiam. Nem todos os objetos são subclassíveis. Mas tudo é um objeto no sentido de que pode ser atribuído a uma variável ou passado como um argumento para uma função.

Você pode ter ouvido o termo “objeto de primeira classe” em outros contextos de programação. Em Python, funções são objetos de primeira classe. Você pode passar uma função como um argumento para outra função. Módulos são objetos de primeira classe. Você pode passar um módulo inteiro como um argumento para uma função. As classes são objetos de primeira classe e as instâncias individuais de uma classe também são objetos de primeira classe.

Isso é importante, então vou repeti-lo caso você tenha perdido nas primeiras vezes: tudo em Python é um objeto. Strings são objetos. Listas são objetos. Funções são objetos. As classes são objetos. As instâncias de classe são objetos. Mesmo os módulos são objetos.

Indentação de código

As funções Python não têm begin ou explícito end e nem chaves para marcar onde o código da função começa e termina. O único delimitador é dois pontos (:) e o recuo do próprio código.

def tamanho_aproximado(tamanho, um_kilobyte_eh_1024_bytes=True): ①
    if tamanho < 0:                                              ②
        raise ValueError('O número não deve ser negativo')       ③
                                                                 ④
    multiplo = 1024 if um_kilobyte_eh_1024_bytes else 1000
    for sufixo in SUFIXOS[multiplo]:                             ⑤
        tamanho /= multiplo
        if tamanho < multiplo:
            return '{0:.1f} {1}'.format(tamanho, sufixo)

    raise ValueError('numero muito longo')
  1. Os blocos de código são definidos por sua indentação. Por “bloco de código”, quero dizer funções, instruções if, loops for, loops while e assim por diante. A indentação inicia um bloco e a desindentação o finaliza. Não há chaves, colchetes ou palavras-chave explícitas. Isso significa que o espaço em branco é significativo e deve ser consistente. Neste exemplo, o código da função tem quatro espaços indentados. Não precisa ter quatro espaços, só precisa ser consistente. A primeira linha sem recuo marca o fim da função.

  2. Em Python, uma instrução if é seguida por um bloco de código. Se a instrução if for avaliada como verdadeira, o bloco recuado é executado, caso contrário, cai para o bloco else (se houver). Observe a falta de parênteses ao redor da expressão.

  3. Esta linha está dentro do bloco de código if. Esta instrução raise gerará uma exceção (do tipo ValueError), mas somente se tamanho < 0.

  4. Este não é o fim da função. Linhas completamente em branco não contam. Eles podem tornar o código mais legível, mas não contam como delimitadores de bloco de código. A função continua na próxima linha.

  5. O loop for também marca o início de um bloco de código. Os blocos de código podem conter várias linhas, desde que todos tenham o mesmo recuo. Este loop for contém três linhas de código. Não há nenhuma outra sintaxe especial para blocos de código de várias linhas. Apenas recue e continue com sua vida.

Depois de alguns protestos iniciais e várias analogias maliciosas com o Fortran, você fará as pazes com isso e começará a ver seus benefícios. Um grande benefício é que todos os programas Python são semelhantes, já que o recuo é um requisito da linguagem e não uma questão de estilo. Isso torna mais fácil ler e entender o código Python de outras pessoas.

Observação

Python usa retornos de carro para separar instruções e dois pontos e indentação para separar blocos de código. C++ e Java usam ponto-e-vírgula para separar instruções e chaves para separar blocos de código.

Exceções

As exceções estão em todos os lugares em Python. Praticamente todos os módulos da biblioteca Python padrão os usam, e o próprio Python os criará em muitas circunstâncias diferentes. Você os verá repetidamente ao longo deste livro.

O que é uma exceção? Normalmente é um erro, uma indicação de que algo deu errado. (Nem todas as exceções são erros, mas não importa por enquanto). Algumas linguagens de programação incentivam o uso de códigos de retorno de erro, que você verifica. Python incentiva o uso de exceções, com as quais você lida.

Quando ocorre um erro no Python Shell, ele imprime alguns detalhes sobre a exceção e como ela aconteceu, e pronto. Isso é chamado de exceção não tratada. Quando a exceção foi levantada, não havia código para notá-la explicitamente e lidar com ela, então ela borbulhou seu caminho de volta ao nível superior do Python Shell, que cospe algumas informações de depuração e o encerrou. No shell, isso não é grande coisa, mas se isso acontecesse enquanto seu programa Python real estivesse em execução, todo o programa iria parar bruscamente se nada tratasse da exceção. Talvez seja isso que você queira, talvez não.

Observação

Ao contrário do Java, as funções Python não declaram quais exceções podem gerar. Depende de você determinar quais possíveis exceções você precisa detectar.

No entanto, uma exceção não precisa resultar em um travamento completo do programa. As exceções podem ser tratadas. Às vezes, uma exceção é realmente porque você tem um bug em seu código (como acessar uma variável que não existe), mas às vezes uma exceção é algo que você pode antecipar. Se você estiver abrindo um arquivo, ele pode não existir. Se você estiver importando um módulo, ele pode não estar instalado. Se você estiver se conectando a um banco de dados, ele pode não estar disponível ou você pode não ter as credenciais de segurança corretas para acessá-lo. Se você sabe que uma linha de código pode gerar uma exceção, deve tratar a exceção usando um bloco try...except.

Observação

Python usa blocos try...except para lidar com exceções e a instrução raise para gerá-las. Java e C++ usam blocos try...catch para lidar com exceções e a instrução throw para gerá-los.

A função tamanho_aproximado() levanta exceções em dois casos diferentes: se o tamanho fornecido for maior do que a função foi projetada para lidar ou se for menor que zero.

if tamanho < 0:
    raise ValueError('O número não deve ser negativo')

A sintaxe para gerar uma exceção é bastante simples. Use a instrução raise, seguida pelo nome da exceção e uma string opcional legível para fins de depuração. A sintaxe lembra a chamada de uma função. (Na realidade, as exceções são implementadas como classes, e essa instrução raise está, na verdade, criando uma instância da classe ValueError e passando a string 'O número não deve ser negativo' para seu método de inicialização. Mas estamos nos adiantando!)

Observação

Você não precisa lidar com uma exceção na função que a gera. Se uma função não lida com isso, a exceção é passada para a função de chamada, a função de chamada dessa função e assim por diante "na pilha". Se a exceção nunca for tratada, seu programa irá travar, o Python imprimirá um “traceback” para o erro padrão e ponto final. Novamente, talvez seja isso que você deseja; depende do que seu programa faz.

Captura de erros de importação

Uma das exceções integradas do Python é ImportError, que é gerada quando você tenta importar um módulo e falha. Isso pode acontecer por vários motivos, mas o caso mais simples é quando o módulo não existe em seu caminho de pesquisa de importação. Você pode usar isso para incluir recursos opcionais em seu programa. Por exemplo, a biblioteca chardet fornece detecção automática de codificação de caracteres. Talvez o seu programa queira usar esta biblioteca, se ela existir, mas continue normalmente se o usuário não a tiver instalado. Você pode fazer isso com um bloco try..except.

try:
    import chardet
except ImportError:
    chardet = None

Posteriormente, você pode verificar a presença do módulo chardet com uma instrução if simples:

if chardet:
  # faça algo
else:
  # continue de qualquer jeito

Outro uso comum da exceção ImportError é quando dois módulos implementam uma API comum, mas um é mais desejável do que o outro. (Talvez seja mais rápido ou use menos memória). Você pode tentar importar um módulo, mas voltar para um módulo diferente se a primeira importação falhar. Por exemplo, o capítulo XML fala sobre dois módulos que implementam uma API comum, chamada API ElementTree. O primeiro lxml é um módulo de terceiros que você precisa fazer o download e instalar por conta própria. O segundo, xml.etree.ElementTree é mais lento, mas faz parte da biblioteca padrão do Python 3.

try:
    from lxml import etree
except ImportError:
    import xml.etree.ElementTree as etree

Ao final deste try..exceptbloco, você importou algum módulo e o nomeou etree . Uma vez que ambos os módulos implementam uma API comum , o resto do seu código não precisa ficar verificando qual módulo foi importado. E uma vez que o módulo que foi importado é sempre chamado de etree , o resto do seu código não precisa estar cheio de ifinstruções para chamar módulos com nomes diferentes.

Variáveis não definidas

Dê uma outra olhada nesta linha de código da função tamanho_aproximado():

multiplo = 1024 if um_kilobyte_eh_1024_bytes else 1000

Você nunca declara a variável multiplo, apenas atribuiu um valor a ela. Tudo bem, porque Python permite que você faça isso. O que Python não vai permitirá que você faça referência a uma variável à qual nunca foi atribuído um valor. Tentar fazer isso gerará uma exceção NameError.

>>> x
Traceback (most recent call last):
  File "<stdin≷", line 1, in <module≷
NameError: name 'x' is not defined
>>> x = 1
>>> x
1

Você vai agradecer ao Python por isso um dia.

Tudo diferencia maiúsculas de minúsculas

Todos os nomes em Python diferenciam maiúsculas de minúsculas: nomes de variáveis, nomes de funções, nomes de classes, nomes de módulos, nomes de exceções. Se você puder obtê-lo, configurá-lo, chamá-lo, construí-lo, importá-lo ou aumentá-lo, ele diferencia maiúsculas de minúsculas.

≷≷≷ um_inteiro = 1
≷≷≷ um_inteiro
1
≷≷≷ UM_INTEIRO
Traceback (most recent call last):
  File "<stdin≷", line 1, in <module≷
NameError: name 'UM_INTEIRO' is not defined
≷≷≷ Um_Inteiro
Traceback (most recent call last):
  File "<stdin≷", line 1, in <module≷
NameError: name 'Um_Inteiro' is not defined
≷≷≷ um_inTeiro
Traceback (most recent call last):
  File "<stdin≷", line 1, in <module≷
NameError: name 'um_inTeiro' is not defined

E assim por diante.

Executando Scripts

Tudo em Python é um objeto.

Módulos Python são objetos e têm vários atributos úteis. Você pode usar isso para testar facilmente seus módulos à medida que os escreve, incluindo um bloco especial de código que é executado quando você executa o arquivo Python na linha de comando. Pegue as últimas linhas de teste.py:

if __name__ == '__main__':
    print(tamanho_aproximado(1000000000000, False))
    print(tamanho_aproximado(1000000000000))

Observação

Como C, Python usa == para comparação e = para atribuição. Ao contrário de C, Python não oferece suporte para atribuição in-line, portanto, não há chance de atribuir acidentalmente o valor que você pensou que estava comparando.

Então, o que torna esta declaração if especial? Bem, os módulos são objetos e todos os módulos têm um atributo __name__ embutido. Um módulo __name__ depende de como você está usando o módulo. Se você for importar o módulo, então __name__ será o nome do arquivo do módulo, sem um caminho de diretório ou extensão de arquivo.

>>> import teste
>>> teste.__name__
'teste'

Mas você também pode executar o módulo diretamente como um programa independente; nesse caso __name__, será um valor padrão especial: __main__. Python avaliará essa instrução if, encontrará uma expressão verdadeira e executará o bloco de código if. Neste caso, para imprimir dois valores.

c:\home\diveintopython3> c:\python31\python.exe humansize.py
1.0 TB
931.3 GiB

E esse é o seu primeiro programa Python!

Leitura Adicional

Esse artigo é uma tradução de um capítulo do livro "Dive Into Python 3" escrito por Mark Pilgrim. Você pode ler o livro desde o início em português clicando aqui.

Traduzido por Acervo Lima. O original pode ser acessado aqui.

Licença