domingo, 9 de maio de 2021

Strings em python 3

❝Estou dizendo isso porque você é um dos meus amigos.
Meu alfabeto começa onde seu alfabeto termina!❞
- Dr. Seuss, On Beyond Zebra!

Algumas coisas chatas que você precisa entender antes de mergulhar

Poucas pessoas pensam nisso, mas o texto é incrivelmente complicado. Comece com o alfabeto. O povo de Bougainville possui o menor alfabeto do mundo; seu alfabeto Rotokas é composto de apenas 12 letras: A, E, G, I, K, O, P, R, S, T, U e V. Na outra extremidade do espectro, idiomas como chinês, japonês e O coreano tem milhares de caracteres. O inglês, é claro, tem 26 letras - 52 se você contar maiúsculas e minúsculas separadamente - mais um punhado de ! @ # $% & Sinais de pontuação.

Quando você fala sobre “texto”, provavelmente está pensando em “caracteres e símbolos na tela do meu computador”. Mas os computadores não lidam com caracteres e símbolos; eles lidam com bits e bytes. Cada pedaço de texto que você já viu na tela do computador é, na verdade, armazenado em uma codificação de caracteres específica. A grosso modo, a codificação de caracteres fornece um mapeamento entre o que você vê na tela e o que seu computador realmente armazena na memória e no disco. Existem muitas codificações de caracteres diferentes, algumas otimizadas para idiomas específicos, como russo, chinês ou inglês, e outras que podem ser usadas para vários idiomas.

Na verdade, é mais complicado do que isso. Muitos caracteres são comuns a várias codificações, mas cada codificação pode usar uma sequência diferente de bytes para realmente armazenar esses caracteres na memória ou no disco. Portanto, você pode pensar na codificação de caracteres como uma espécie de chave de descriptografia. Sempre que alguém lhe dá uma sequência de bytes - um arquivo, uma página da web, qualquer coisa - e afirma que é um “texto”, você precisa saber qual codificação de caracteres eles usaram para poder decodificar os bytes em caracteres. Se eles derem a chave errada ou nenhuma chave, você terá a tarefa nada invejável de decifrar o código sozinho. Provavelmente, você errará e o resultado será um jargão.

Tudo o que você pensava que sabia sobre strings está errado.

Certamente você já viu páginas da web como esta, com estranhos caracteres de ponto de interrogação onde deveriam estar apóstrofos. Isso geralmente significa que o autor da página não declarou sua codificação de caracteres corretamente, seu navegador ficou adivinhando e o resultado foi uma mistura de caracteres esperados e inesperados. Em inglês, é simplesmente irritante; em outros idiomas, o resultado pode ser completamente ilegível.

Existem codificações de caracteres para cada idioma principal do mundo. Como cada idioma é diferente e a memória e o espaço em disco são historicamente caros, cada codificação de caractere é otimizada para um idioma específico. Com isso, quero dizer que cada codificação usa os mesmos números (0–255) para representar os caracteres desse idioma. Por exemplo, você provavelmente está familiarizado com a codificação ASCII, que armazena caracteres em inglês como números que variam de 0 a 127. (65 é "A" maiúsculo, 97 é "a" minúsculo. O inglês tem um alfabeto muito simples, para que possa ser totalmente expresso em menos de 128 números. Para aqueles que podem contar na base 2, são 7 dos 8 bits em um byte.

Os idiomas da Europa Ocidental, como francês, espanhol e alemão, têm mais letras do que o inglês. Ou, mais precisamente, eles têm letras combinadas com vários sinais diacríticos, como o caractere ñ em espanhol. A codificação mais comum para esses idiomas é CP-1252, também chamada de “windows-1252” porque é amplamente usada no Microsoft Windows. A codificação CP-1252 compartilha caracteres com ASCII no intervalo 0-127, mas depois se estende para o intervalo 128-255 para caracteres como n-com-um-til sobre ele (241), u-com-dois-pontos sobre ele (252). No entanto, ainda é uma codificação de byte único; o maior número possível, 255, ainda cabe em um byte.

Depois, há idiomas como chinês, japonês e coreano, que têm tantos caracteres que exigem conjuntos de caracteres de bytes múltiplos. Ou seja, cada “caractere” é representado por um número de dois bytes de 0–65535. Mas diferentes codificações multibyte ainda compartilham o mesmo problema que diferentes codificações de um único byte, ou seja, que cada uma usa os mesmos números para significar coisas diferentes. Acontece que a gama de números é mais ampla, porque há muito mais caracteres para representar.

Isso era normal em um mundo sem rede, onde “texto” era algo que você digitava e ocasionalmente imprimia. Não havia muito “texto simples”. O código-fonte foi ASCII, e todos os outros utilizados processadores de texto, que definiram seus próprios formatos (não-texto) que seguiram informações codificação de caracteres, juntamente com um estilo rico. As pessoas liam esses documentos com o mesmo processador de texto do autor original, então tudo funcionava, mais ou menos.

Agora pense no surgimento de redes globais como e-mail e web. Muito “texto simples” voando ao redor do globo, sendo escrito em um computador, transmitido por um segundo computador e recebido e exibido por um terceiro computador. Os computadores só podem ver números, mas os números podem significar coisas diferentes. Ah não! O que fazer? Bem, os sistemas tiveram que ser projetados para transportar informações de codificação junto com cada pedaço de "texto simples". Lembre-se de que é a chave de descriptografia que mapeia números legíveis por computador em caracteres legíveis por humanos. Uma chave de descriptografia ausente significa texto truncado, jargão ou pior.

Agora pense em tentar armazenar vários trechos de texto no mesmo lugar, como na mesma tabela de banco de dados que contém todos os e-mails que você já recebeu. Você ainda precisa armazenar a codificação de caracteres ao lado de cada pedaço de texto para que possa exibi-lo corretamente. Acha que é difícil? Tente pesquisar em seu banco de dados de e-mail, o que significa converter entre várias codificações instantaneamente. Não parece divertido?

Agora pense na possibilidade de documentos multilíngues, onde caracteres de vários idiomas estão próximos uns dos outros no mesmo documento. (Dica: os programas que tentavam fazer isso normalmente usavam códigos de escape para alternar os "modos". Puf, você está no modo koi8-r russo, então 241 significa Я; puf, agora você está no modo grego Mac, então 241 significa ώ.) E, claro, você também desejará pesquisar esses documentos.

Agora chore muito, porque tudo que você pensava que sabia sobre strings está errado, e não existe "texto simples".

Unicode

Digite Unicode.

Unicode é um sistema projetado para representar todos os caracteres de todos os idiomas. Unicode representa cada letra, caractere ou ideograma como um número de 4 bytes. Cada número representa um caractere único usado em pelo menos um dos idiomas do mundo. (Nem todos os números são usados, mas mais de 65535 deles, portanto, 2 bytes não seriam suficientes.) Os caracteres usados em vários idiomas geralmente têm o mesmo número, a menos que haja uma boa razão etimológica para não o fazer. Independentemente disso, há exatamente 1 número por caractere e exatamente 1 caractere por número. Cada número sempre significa apenas uma coisa; não há “modos” para acompanhar. U+0041 é sempre 'A', mesmo que seu idioma não contenha um 'A'.

Diante disso, parece uma ótima ideia. Uma codificação para governar todos eles. Vários idiomas por documento. Não há mais "troca de modo" para alternar entre as codificações no meio do fluxo. Mas, de imediato, a pergunta óbvia deve saltar para você. Quatro bytes? Para cada caractere Isso parece muito desperdício, especialmente para idiomas como Inglês e Espanhol, que precisam de menos de um byte (256 números) para expressar cada caractere possível. Na verdade, é um desperdício até mesmo para idiomas baseados em ideogramas (como o chinês), que nunca precisam de mais de dois bytes por caractere.

Existe uma codificação Unicode que usa quatro bytes por caractere. É chamado de UTF-32, porque 32 bits = 4 bytes. UTF-32 é uma codificação direta; ele pega cada caractere Unicode (um número de 4 bytes) e representa o caractere com esse mesmo número. Isso tem algumas vantagens, a mais importante é que você pode encontrar o enésimo caractere de uma string em tempo constante, porque o enésimo caractere começa no 4 × enésimo byte. Ele também tem várias desvantagens, sendo a mais óbvia que são necessários quatro bytes para armazenar cada caractere estranho.

Mesmo que haja muitos caracteres Unicode, acontece que a maioria das pessoas nunca usará nada além do primeiro 65535. Portanto, existe outra codificação Unicode, chamada UTF-16 (porque 16 bits = 2 bytes). UTF-16 codifica cada caractere de 0-65535 como dois bytes, então usa alguns truques sujos se você realmente precisar representar os caracteres Unicode do "plano astral" raramente usados ​​além de 65535. Vantagem mais óbvia: UTF-16 é duas vezes mais espaço- eficiente como UTF-32, porque cada caractere requer apenas dois bytes para armazenar em vez de quatro bytes (exceto para aqueles que não o fazem). E você ainda pode encontrar facilmente o enésimo caractere de uma string em tempo constante, se assumir que a string não inclui nenhum caractere do plano astral, o que é uma boa suposição até o momento em que não é.

Mas também há desvantagens não óbvias para UTF-32 e UTF-16. Diferentes sistemas de computador armazenam bytes individuais de maneiras diferentes. Isso significa que o caractere U+4E2D pode ser armazenado em UTF-16 como 4E 2D ou 2D 4E, dependendo se o sistema é big-endian ou little-endian. (Para UTF-32, há ainda mais ordens de bytes possíveis). Contanto que seus documentos nunca saiam do computador, você está seguro - aplicativos diferentes no mesmo computador usarão a mesma ordem de bytes. Mas no minuto em que você quiser transferir documentos entre sistemas, talvez em uma rede mundial de computadores de algum tipo, precisará de uma maneira de indicar em qual ordem seus bytes são armazenados. Caso contrário, o sistema receptor não tem como saber se a sequência de dois bytes 4E 2D significa U+4E2D ou U+2D4E.

Para resolver este problema, as codificações Unicode multibyte definem uma "Marca de Ordem de Byte", que é um caractere especial não imprimível que você pode incluir no início do seu documento para indicar em que ordem seus bytes estão. Para UTF-16, a Marca de Ordem de Byte é U+FEFF. Se você receber um documento UTF-16 que começa com os bytes FF FE, saberá que a ordem dos bytes é uma maneira; se começar com FE FF, você sabe que a ordem dos bytes está invertida.

Ainda assim, o UTF-16 não é exatamente ideal, especialmente se você estiver lidando com muitos caracteres ASCII. Se você pensar bem, até mesmo uma página da web chinesa conterá muitos caracteres ASCII - todos os elementos e atributos que cercam os caracteres chineses imprimíveis. Ser capaz de encontrar o enésimo caractere em tempo constante é bom, mas ainda há o problema irritante desses caracteres do plano astral, o que significa que você não pode garantir que cada caractere tenha exatamente dois bytes, então você não pode realmente encontrar o enésimo caractere em tempo constante, a menos que você mantenha um índice separado. E cara, com certeza há muito texto ASCII no mundo...

Outras pessoas ponderaram essas questões e encontraram uma solução:

UTF-8

UTF-8 é um sistema de codificação de comprimento variável para Unicode. Ou seja, caracteres diferentes ocupam um número diferente de bytes. Para ASCII caracteres (AZ). UTF-8 usa apenas um byte por caractere. Na verdade, ele usa exatamente os mesmos bytes; os primeiros 128 caracteres (0–127) em UTF-8 são indistinguíveis de ASCII. Os caracteres “latinos estendidos” como ñ e ö acabam ocupando dois bytes. (Os bytes não são simplesmente o ponto de código Unicode como seriam em UTF-16; há algumas mudanças sérias de bits envolvidas). Caracteres chineses como 中 acabam ocupando três bytes. Os caracteres raramente usados do “plano astral” ocupam quatro bytes.

Desvantagens: como cada caractere pode ter um número diferente de bytes, encontrar o enésimo caractere é uma operação O(N) - ou seja, quanto mais longa a string, mais tempo leva para encontrar um caractere específico. Além disso, há um ajuste de bits envolvido para codificar caracteres em bytes e decodificar bytes em caracteres.

Vantagens: codificação supereficiente de caracteres ASCII comuns. Não é pior do que UTF-16 para caracteres latinos estendidos. Melhor do que UTF-32 para caracteres chineses. Além disso (e você terá que confiar em mim nisso, porque não vou mostrar a matemática), devido à natureza exata da manipulação de bits, não há problemas de ordenação de bytes. Um documento codificado em UTF-8 usa exatamente o mesmo fluxo de bytes em qualquer computador.

Mergulho

No Python 3, todas as strings são sequências de caracteres Unicode. Não existe uma string Python codificada em UTF-8, ou uma string Python codificada como CP-1252. “Esta string é UTF-8?” é uma pergunta inválida. UTF-8 é uma forma de codificar caracteres como uma sequência de bytes. Se você quiser pegar uma string e transformá-la em uma sequência de bytes em uma codificação de caracteres específica, o Python 3 pode ajudá-lo com isso. Se você quiser pegar uma sequência de bytes e transformá-la em uma string, o Python 3 pode ajudá-lo com isso também. Bytes não são caracteres; bytes são bytes. Caracteres são uma abstração. Uma string é uma sequência dessas abstrações.

>>> s = '深入 Python'    ①
>>> len(s)               ②
9
>>> s[0]                 ③
'深'
>>> s + ' 3'             ④
'深入 Python 3'
  1. Para criar uma string, coloque-a entre aspas. As strings Python podem ser definidas com aspas simples (') ou aspas duplas (").
  2. A função len() interna retorna o comprimento da string, ou seja, o número de caracteres. Esta é a mesma função que você usa para encontrar o comprimento de uma lista, tupla, conjunto ou dicionário. Uma string é como uma tupla de caracteres.
  3. Assim como obter itens individuais de uma lista, você pode obter caracteres individuais de uma string usando a notação de índice.
  4. Assim como nas listas, você pode concatenar strings usando o operador +.

Formatando Strings

As strings podem ser definidas com aspas simples ou duplas.

Vamos dar outra olhada em humansize.py:

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

def approximate_size(size, a_kilobyte_is_1024_bytes=True):
    '''Convert a file size to human-readable form.                          ②

    Keyword arguments:
    size -- file size in bytes
    a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024
                                if False, use multiples of 1000

    Returns: string

    '''                                                                     ③
    if size < 0:
        raise ValueError('number must be non-negative')                     ④

    multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
    for suffix in SUFFIXES[multiple]:
        size /= multiple
        if size < multiple:
            return '{0:.1f} {1}'.format(size, suffix)                       ⑤

    raise ValueError('number too large')
  1. 'KB', 'MB', 'GB'... esses são cada strings.
  2. As docstrings de função são strings. Esta docstring abrange várias linhas, portanto, ela usa três aspas em uma linha para iniciar e terminar a string.
  3. Essas três aspas em uma linha encerram a docstring.
  4. Há outra string, sendo passada para a exceção como uma mensagem de erro legível.
  5. Há um... uau, o que diabos é isso?

Python 3 oferece suporte à formatação de valores em strings. Embora isso possa incluir expressões muito complicadas, o uso mais básico é inserir um valor em uma string com um único espaço reservado.

>>> username = 'mark'
>>> password = 'PapayaWhip'                             ①
>>> "{0}'s password is {1}".format(username, password)  ②
"mark's password is PapayaWhip"
  1. Não, minha senha não é realmente PapayaWhip.
  2. Há muita coisa acontecendo aqui. Primeiro, essa é uma chamada de método em um literal de string. Strings são objetos e objetos têm métodos. Em segundo lugar, toda a expressão é avaliada como uma string. Terceiro, {0} e {1} são campos de substituição, que são substituídos pelos argumentos passados ​​para o método format().

Nomes de campos compostos

O exemplo anterior mostra o caso mais simples, onde os campos de substituição são simplesmente inteiros. Os campos de substituição de inteiros são tratados como índices posicionais na lista de argumentos do método format(). Isso significa que {0} é substituído pelo primeiro argumento (username, neste caso), {1} é substituído pelo segundo argumento (password). Você pode ter tantos índices posicionais quantos argumentos e quantos argumentos quiser. Mas os campos de substituição são muito mais poderosos do que isso.

>>> import humansize
>>> si_suffixes = humansize.SUFFIXES[1000]      ①
>>> si_suffixes
['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
>>> '1000{0[0]} = 1{0[1]}'.format(si_suffixes)  ②
'1000KB = 1MB'
  1. Em vez de chamar qualquer função no módulo humansize, você está apenas pegando uma das estruturas de dados que ela define: a lista de sufixos “SI” (potências de 1000).
  2. Parece complicado, mas não é. {0} faria referência ao primeiro argumento passado ao método format(), si_suffixes. Mas si_suffixes é uma lista. Assim, {0[0]} refere-se ao primeiro item da lista que é o primeiro argumento passado para o método format(): 'KB'. Enquanto isso, {0[1]} refere-se ao segundo item da mesma lista: 'MB'. Tudo fora das chaves - incluindo 1000, o sinal de igual e os espaços - permanece intocado. O resultado final é a string '1000KB = 1MB'.

{0} é substituído pelo primeiro argumento de format(). {1} é substituído pelo 2º.

O que este exemplo mostra é que os especificadores de formato podem acessar itens e propriedades de estruturas de dados usando (quase) a sintaxe Python . Isso é chamado de nomes de campos compostos . Os seguintes nomes de campos compostos “simplesmente funcionam”:

  • Passar uma lista e acessar um item da lista por índice (como no exemplo anterior).
  • Passando um dicionário e acessando um valor do dicionário por chave.
  • Passar um módulo e acessar suas variáveis e funções por nome.
  • Passar uma instância de classe e acessar suas propriedades e métodos por nome.
  • Qualquer combinação das opções acima

Só para te impressionar, aqui está um exemplo que combina todas as opções acima:

>>> import humansize
>>> import sys
>>> '1MB = 1000{0.modules[humansize].SUFFIXES[1000][0]}'.format(sys)
'1MB = 1000KB'

Funciona assim:

  • O módulo sys contém informações sobre a instância do Python em execução no momento. Como você acabou de importá-lo, pode passar o próprio módulo sys como um argumento para o método format(). Portanto, o campo de substituição {0} refere-se ao módulo sys.
  • sys.modules é um dicionário de todos os módulos que foram importados nesta instância Python. As chaves são os nomes dos módulos como strings; os valores são os próprios objetos do módulo. Portanto, o campo de substituição {0.modules} refere-se ao dicionário de módulos importados.
  • sys.modules['humansize'] é o módulo humansize que você acabou de importar. O campo de substituição {0.modules[humansize]} refere-se ao módulo humansize. Observe a ligeira diferença de sintaxe aqui. No código Python real, as chaves do dicionário sys.modules são strings; para se referir a eles, você precisa colocar aspas ao redor do nome do módulo (por exemplo 'humansize'). Mas dentro de um campo de substituição, você pula as aspas em torno do nome da chave do dicionário (por exemplo humansize). Para citar o PEP 3101: Advanced String Formatting, “As regras para analisar uma chave de item são muito simples. Se começar com um dígito, é tratado como um número, caso contrário, é usado como uma string”.
  • sys.modules['humansize'].SUFFIXES é o dicionário definido na parte superior do módulo humansize. O campo de substituição {0.modules[humansize].SUFFIXES} refere-se a esse dicionário.
  • sys.modules['humansize'].SUFFIXES[1000] é uma lista de sufixos SI: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']. Portanto, o campo de substituição {0.modules[humansize].SUFFIXES[1000]} se refere a essa lista.
  • sys.modules['humansize'].SUFFIXES[1000][0] é o primeiro item da lista de sufixos SI: 'KB'. Portanto, o campo de substituição completo {0.modules[humansize].SUFFIXES[1000][0]} é substituído pela sequência de dois caracteres KB.

Especificadores de formato

Mas espere! Tem mais! Vamos dar uma outra olhada nessa estranha linha de código de humansize.py:

if size < multiple:
    return '{0:.1f} {1}'.format(size, suffix)

{1} é substituído pelo segundo argumento passado ao método format(), que é o suffix. Mas o que é {0:.1f}? São duas coisas: {0} que você reconhece e :.1f que não. A segunda metade (incluindo e após os dois pontos) define o especificador de formato, que refina ainda mais como a variável substituída deve ser formatada.

Observação

Os especificadores de formato permitem que você misture o texto de substituição de várias maneiras úteis, como a função printf() em C. Você pode adicionar zero ou espaçamento, alinhar strings, controlar a precisão decimal e até mesmo converter números em hexadecimais.

Em um campo de substituição, dois pontos (:) marcam o início do especificador de formato. O especificador de formato “.1” significa “arredondar para o décimo mais próximo” (ou seja, exibir apenas um dígito após a vírgula decimal). O especificador de formato “f” significa “número de ponto fixo” (em oposição à notação exponencial ou alguma outra representação decimal). Assim, dado um size de 698.24 e suffix de 'GB', a string formatada seria '698.2 GB', porque 698.24 é arredondado para uma casa decimal, então o sufixo é anexado após o número.

>>> '{0:.1f} {1}'.format(698.24, 'GB')
'698.2 GB'

Para todos os detalhes sangrentos sobre especificadores de formato, consulte a Minilinguagem de Especificação de Formato na documentação oficial do Python.

Outros métodos de string comuns

Além da formatação, as strings podem fazer vários outros truques úteis.

>>> s = '''Finished files are the re-  ①
... sult of years of scientif-
... ic study combined with the
... experience of years.'''
>>> s.splitlines()                     ②
['Finished files are the re-',
 'sult of years of scientif-',
 'ic study combined with the',
 'experience of years.']
>>> print(s.lower())                   ③
finished files are the re-
sult of years of scientif-
ic study combined with the
experience of years.
>>> s.lower().count('f')               ④
6
  1. Você pode inserir strings de várias linhas no shell interativo do Python. Depois de iniciar uma string de várias linhas com aspas triplas, apenas pressione ENTER e o shell interativo solicitará que você continue a string. Digitar as aspas triplas de fechamento encerra a string e o próximo ENTER executará o comando (neste caso, atribuindo a string a s).
  2. O método splitlines() pega uma string de várias linhas e retorna uma lista de strings, uma para cada linha do original. Observe que os retornos de carro no final de cada linha não estão incluídos.
  3. O método lower() converte toda a string em minúsculas. (Da mesma forma, o método upper() converte uma string em maiúsculas).
  4. O método count() conta o número de ocorrências de uma substring. Sim, realmente existem seis “f”s nessa frase!

Aqui está outro caso comum. Digamos que você tenha uma lista de pares de valores-chave no formato key1=value1&key2=value2 e queira dividi-los e fazer um dicionário no formato {key1: value1, key2: value2}.

>>> query = 'user=pilgrim&database=master&password=PapayaWhip'
>>> a_list = query.split('&')                                        ①
>>> a_list
['user=pilgrim', 'database=master', 'password=PapayaWhip']
>>> a_list_of_lists = [v.split('=', 1) for v in a_list if '=' in v]  ②
>>> a_list_of_lists
[['user', 'pilgrim'], ['database', 'master'], ['password', 'PapayaWhip']]
>>> a_dict = dict(a_list_of_lists)                                   ③
>>> a_dict
{'password': 'PapayaWhip', 'user': 'pilgrim', 'database': 'master'}
  1. O método split() da string tem um argumento obrigatório, um delimitador. O método divide uma string em uma lista de strings com base no delimitador. Aqui, o delimitador é um caractere e comercial, mas pode ser qualquer coisa.
  2. Agora temos uma lista de strings, cada uma com uma chave, seguida por um sinal de igual, seguida por um valor. Podemos usar uma compreensão de lista para iterar por toda a lista e dividir cada string em duas strings com base no primeiro sinal de igual. O segundo argumento opcional para o método split() é o número de vezes que você deseja dividir. 1 significa “dividir apenas uma vez”, portanto, o método split() retornará uma lista de dois itens. (Em teoria, um valor também pode conter um sinal de igual. Se você acabou de usar 'key=value=foo'.split('='), acabará com uma lista de três itens ['key', 'value', 'foo']).
  3. Por fim, o Python pode transformar essa lista de listas em um dicionário simplesmente passando-a para a função dict().

Observação

O exemplo anterior se parece muito com a análise de parâmetros de consulta numa URL, mas a análise de URL na vida real é, na verdade, mais complicada do que isso. Se você estiver lidando com parâmetros de consulta de URL, é melhor usar a função urllib.parse.parse_qs(), que lida com alguns casos extremos não óbvios.

Cortando uma string

Depois de definir uma string, você pode obter qualquer parte dela como uma nova string. Isso é chamado de fatiar uma string. Fatiar (slicing) strings funciona exatamente da mesma forma que fatiar listas, o que faz sentido, porque strings são apenas sequências de caracteres.

>>> a_string = 'My alphabet starts where your alphabet ends.'
>>> a_string[3:11]           ①
'alphabet'
>>> a_string[3:-3]           ②
'alphabet starts where your alphabet en'
>>> a_string[0:2]            ③
'My'
>>> a_string[:18]            ④
'My alphabet starts'
>>> a_string[18:]            ⑤
' where your alphabet ends.'
  1. Você pode obter uma parte de uma string, chamada de “fatia”, especificando dois índices. O valor de retorno é uma nova string contendo todos os caracteres da string, em ordem, começando com o índice da primeira fatia.
  2. Como listas de fatiamento, você pode usar índices negativos para fatiar strings.
  3. As strings são baseadas em zero, portanto, a_string[0:2] retorna os dois primeiros itens da string, começando em a_string[0], até, mas não incluindo a_string[2].
  4. Se o índice da fatia esquerda for 0, você pode deixá-lo de fora e 0 está implícito. Então a_string[:18] é o mesmo que a_string[0:18], porque o 0 inicial está implícito.
  5. Da mesma forma, se o índice de fatia correto for o comprimento da string, você pode deixá-lo de fora. Então a_string[18:] é o mesmo que a_string[18:44], porque esta string tem 44 caracteres. Há uma simetria agradável aqui. Nesta sequência de 44 caracteres, a_string[:18] retorna os primeiros 18 caracteres e a_string[18:] retorna tudo, exceto os primeiros 18 caracteres. Na verdade, a_string[:n] sempre retornará os primeiros n caracteres e a_string[n:] retornará o resto, independentemente do comprimento da string.

Strings vs. Bytes

Bytes são bytes; os caracteres são uma abstração. Uma sequência imutável de caracteres Unicode é chamada de string. Uma sequência imutável de números entre 0 e 255 é chamada de objeto de bytes.

>>> by = b'abcd\x65'  ①
>>> by
b'abcde'
>>> type(by)          ②
<class 'bytes'>
>>> len(by)           ③
5
>>> by += b'\xff'     ④
>>> by
b'abcde\xff'
>>> len(by)           ⑤
6
>>> by[0]             ⑥
97
>>> by[0] = 102       ⑦
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'bytes' object does not support item assignment
  1. Para definir um objecto bytes, b'' utilizar a sintaxe “byte literal”. Cada byte dentro do literal de byte pode ser um caractere ASCII ou um número hexadecimal codificado de \x00 a \xff (0-255).
  2. O tipo de objeto bytes é bytes.
  3. Assim como listas e strings, você pode obter o comprimento de um objeto bytes com a função interna len().
  4. Assim como listas e strings, você pode usar o operador + para concatenar objetos bytes. O resultado é um novo objeto bytes.
  5. Concatenar um objeto bytes de 5 bytes e um objeto de 1 byte bytesfornece um objeto bytes de 6 bytes.
  6. Assim como listas e strings, você pode usar a notação de índice para obter bytes individuais em um objeto bytes. Os itens de uma string são strings; os itens de um objeto bytes são inteiros. Especificamente, números inteiros entre 0–255.
  7. Um objeto bytes é imutável; você não pode atribuir bytes individuais. Se precisar alterar bytes individuais, você pode usar o fatiamento de string e os operadores de concatenação (que funcionam da mesma forma que as strings) ou pode converter o objeto bytes em um objeto bytearray.
>>> by = b'abcd\x65'
>>> barr = bytearray(by)  ①
>>> barr
bytearray(b'abcde')
>>> len(barr)             ②
5
>>> barr[0] = 102         ③
>>> barr
bytearray(b'fbcde')
  1. Para converter um objeto bytes em um objeto mutável bytearray, use a função interna bytearray().
  2. Todos os métodos e operações que você pode fazer em um objeto bytes, você pode fazer em um objeto bytearray também.
  3. A única diferença é que, com o objeto bytearray, você pode atribuir bytes individuais usando a notação de índice. O valor atribuído deve ser um número inteiro entre 0–255.

A única coisa que você nunca pode fazer é misturar bytes e strings.

>>> by = b'd'
>>> s = 'abcde'
>>> by + s                       ①
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't concat bytes to str
>>> s.count(by)                  ②
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't convert 'bytes' object to str implicitly
>>> s.count(by.decode('ascii'))  ③
1
  1. Você não pode concatenar bytes e strings. Eles são dois tipos de dados diferentes.
  2. Você não pode contar as ocorrências de bytes em uma string, porque não há bytes em uma string. Uma string é uma sequência de caracteres. Talvez você quisesse dizer “contar as ocorrências da string que obteria após decodificar essa sequência de bytes em uma codificação de caracteres específica”? Bem, então você precisa dizer isso explicitamente. Python 3 não converterá implicitamente bytes em strings ou strings em bytes.
  3. Por uma coincidência incrível, esta linha de código diz "conte as ocorrências da string que você obteria após decodificar esta sequência de bytes nesta codificação de caractere específica".

E aqui está o link entre strings e bytes: os objetos bytes têm um método decode() que recebe uma codificação de caracteres e retorna uma string, e as strings têm um método encode() que recebe uma codificação de caracteres e retorna um objeto bytes. No exemplo anterior, a decodificação foi relativamente direta - convertendo uma sequência de bytes na codificação ASCII em uma string de caracteres. Mas o mesmo processo funciona com qualquer codificação que suporte os caracteres da string - mesmo codificações legadas (não Unicode).

>>> a_string = '深入 Python'         ①
>>> len(a_string)
9
>>> by = a_string.encode('utf-8')    ②
>>> by
b'\xe6\xb7\xb1\xe5\x85\xa5 Python'
>>> len(by)
13
>>> by = a_string.encode('gb18030')  ③
>>> by
b'\xc9\xee\xc8\xeb Python'
>>> len(by)
11
>>> by = a_string.encode('big5')     ④
>>> by
b'\xb2`\xa4J Python'
>>> len(by)
11
>>> roundtrip = by.decode('big5')    ⑤
>>> roundtrip
'深入 Python'
>>> a_string == roundtrip
True
  1. Isso é uma string. Possui nove caracteres.
  2. Este é um objeto bytes. Possui 13 bytes. É a sequência de bytes que você obtém quando pega a_string e a codifica em UTF-8.
  3. Este é um objeto bytes. Possui 11 bytes. É a sequência de bytes que você obtém quando pega a_string e a codifica em GB18030.
  4. Este é um objeto bytes. Possui 11 bytes. É uma sequência de bytes totalmente diferente que você obtém quando pega a_string e a codifica em Big5.
  5. Isso é uma string. Possui nove caracteres. É a sequência de caracteres que você começa quando você toma by e decodificá-lo usando a codificação algoritmo Big5. É idêntico ao string original.

PostScript: codificação de caracteres do código-fonte do Python

Python 3 assume que seu código-fonte -  ou seja, cada arquivo .py - está codificado em UTF-8.

Observação

No Python 2, a codificação padrão para arquivos .py era ASCII. No Python 3, a codificação padrão é UTF-8.

Se quiser usar uma codificação diferente em seu código Python, você pode colocar uma declaração de codificação na primeira linha de cada arquivo. Esta declaração define um arquivo .py como windows-1252:

# -*- coding: windows-1252 -*-

Tecnicamente, a substituição da codificação de caracteres também pode estar na segunda linha, se a primeira linha for um comando hash-bang semelhante ao UNIX.

#!/usr/bin/python3
# -*- coding: windows-1252 -*-

Para obter mais informações, consulte PEP 263: Definindo Python Source Code Encodings.

Leitura Adicional

Em Unicode em Python:

No Unicode em geral:

Na codificação de caracteres em outros formatos:

Em strings e formatação de strings:

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

Compreensões de listas, dicionários e sets em python

❝Nossa imaginação é esticada ao máximo, não, como na ficção, para imaginar coisas que não existem realmente, mas apenas para compreender as coisas que existem.❞
- Richard Feynman

Mergulho

Cada linguagem de programação tem aquele único recurso, algo complicado intencionalmente simplificado. Se você está vindo de outra linguagem, pode facilmente perdê-lo, porque sua antiga linguagem não tornava isso simples (porque estava ocupado fazendo outra coisa simples). Este capítulo irá ensiná-lo sobre compreensões de lista, compreensões de dicionário e compreensões de conjunto: três conceitos relacionados centrados em torno de uma técnica muito poderosa. Mas, primeiro, quero fazer um pequeno desvio em dois módulos que o ajudarão a navegar em seu sistema de arquivos local.

Trabalho com arquivos e diretórios

Python 3 vem com um módulo chamado os, que significa “sistema operacional”. O módulo os contém uma infinidade de funções para obter informações - e, em alguns casos, manipular - diretórios locais, arquivos, processos e variáveis de ambiente. Python faz o seu melhor para oferecer uma API unificada em todos os sistemas operacionais suportados para que seus programas possam ser executados em qualquer computador com o mínimo possível de código específico de plataforma.

O diretório de trabalho atual

Quando você está começando a usar o Python, vai passar muito tempo no Python Shell. Ao longo deste livro, você verá exemplos semelhantes a este:

  1. Importe um dos módulos da pasta examples
  2. Chame uma função nesse módulo
  3. Explique o resultado

Sempre há um diretório de trabalho atual.

Se você não souber sobre o diretório de trabalho atual, a etapa 1 provavelmente falhará com um ImportError. Por quê? Porque o Python procurará o módulo de exemplo no caminho de pesquisa de importação, mas não o encontrará porque a pasta examples não é um dos diretórios no caminho de pesquisa. Para superar isso, você pode fazer uma das duas coisas:

  1. Adicione a pasta examples ao caminho de pesquisa de importação.
  2. Mude o diretório de trabalho atual para a pasta examples.

O diretório de trabalho atual é uma propriedade invisível que o Python mantém na memória o tempo todo. Sempre há um diretório de trabalho atual, esteja você no Python Shell, executando seu próprio script Python na linha de comando ou executando um script Python CGI em um servidor da web em algum lugar.

O módulo os contém duas funções para lidar com o diretório de trabalho atual.

>>> import os                                            ①
>>> print(os.getcwd())                                   ②
C:\Python31
>>> os.chdir('/Users/pilgrim/diveintopython3/examples')  ③
>>> print(os.getcwd())                                   ④
C:\Users\pilgrim\diveintopython3\examples
  1. O módulo os vem com Python; você pode importá-lo a qualquer hora, em qualquer lugar.
  2. Use a função os.getcwd() para obter o diretório de trabalho atual. Quando você executa o Python Shell gráfico, o diretório de trabalho atual começa como o diretório onde está o executável do Python Shell. No Windows, isso depende de onde você instalou o Python; o diretório padrão é c:\Python31. Se você executar o Python Shell a partir da linha de comando, o diretório de trabalho atual começa como o diretório em que você estava quando executou python3.
  3. Use a função os.chdir() para alterar o diretório de trabalho atual.
  4. Quando chamei a função os.chdir(), usei um nome de caminho no estilo Linux (barras, sem letra de unidade), embora esteja no Windows. Este é um dos lugares onde o Python tenta esconder as diferenças entre os sistemas operacionais.

Trabalho com nomes de arquivos e diretórios

Já que estamos falando de diretórios, quero destacar o módulo os.path. os.path contém funções para manipular nomes de arquivos e nomes de diretórios.

>>> import os
>>> print(os.path.join('/Users/pilgrim/diveintopython3/examples/', 'humansize.py'))              ①
/Users/pilgrim/diveintopython3/examples/humansize.py
>>> print(os.path.join('/Users/pilgrim/diveintopython3/examples', 'humansize.py'))               ②
/Users/pilgrim/diveintopython3/examples\humansize.py
>>> print(os.path.expanduser('~'))                                                               ③
c:\Users\pilgrim
>>> print(os.path.join(os.path.expanduser('~'), 'diveintopython3', 'examples', 'humansize.py'))  ④
c:\Users\pilgrim\diveintopython3\examples\humansize.py
  1. A função os.path.join() constrói um nome de caminho a partir de um ou mais nomes de caminho parciais. Nesse caso, ele simplesmente concatena strings.
  2. Neste caso um pouco menos trivial, chamar a função os.path.join() adicionará uma barra extra ao nome do caminho antes de uni-lo ao nome do arquivo. É uma barra invertida em vez de uma barra normal, porque construí este exemplo no Windows. Se você replicar este exemplo no Linux ou Mac OS X, verá uma barra. Não mexa com barras; sempre use os.path.join() e deixe o Python fazer a coisa certa.
  3. A função os.path.expanduser() irá expandir um nome de caminho que usa ~ para representar o diretório pessoal do usuário atual. Isso funciona em qualquer plataforma em que os usuários tenham um diretório inicial, incluindo Linux, Mac OS X e Windows. O caminho retornado não tem uma barra final, mas a função os.path.join() não se importa.
  4. Combinando essas técnicas, você pode construir facilmente nomes de caminho para diretórios e arquivos no diretório pessoal do usuário. A função os.path.join() pode receber qualquer número de argumentos. Fiquei muito feliz quando descobri isso, já que addSlashIfNecessary() é uma das pequenas funções estúpidas que sempre preciso escrever ao construir minha caixa de ferramentas em uma nova linguagem. Não escreva esta pequena função estúpida em Python; pessoas inteligentes já cuidaram disso para você.

os.path também contém funções para dividir nomes de caminhos completos, nomes de diretórios e nomes de arquivos em suas partes constituintes.

>>> pathname = '/Users/pilgrim/diveintopython3/examples/humansize.py'
>>> os.path.split(pathname)                                        ①
('/Users/pilgrim/diveintopython3/examples', 'humansize.py')
>>> (dirname, filename) = os.path.split(pathname)                  ②
>>> dirname                                                        ③
'/Users/pilgrim/diveintopython3/examples'
>>> filename                                                       ④
'humansize.py'
>>> (shortname, extension) = os.path.splitext(filename)            ⑤
>>> shortname
'humansize'
>>> extension
'.py'
  1. A função split divide um caminho completo e retorna uma tupla contendo o caminho e o nome do arquivo.
  2. Lembra quando eu disse que você poderia usar a atribuição de várias variáveis para retornar vários valores de uma função? A função os.path.split() faz exatamente isso. Você atribui o valor de retorno da função split em uma tupla de duas variáveis. Cada variável recebe o valor do elemento correspondente da tupla retornada.
  3. A primeira variável, dirname, recebe o valor do primeiro elemento da tupla retornada da função os.path.split(), o caminho do arquivo.
  4. A segunda variável, nome do arquivo, recebe o valor do segundo elemento da tupla retornado da função os.path.split(), o nome do arquivo.
  5. os.path também contém a função os.path.splitext(), que divide o nome do arquivo e retorna uma tupla contendo o nome do arquivo e a extensão do arquivo. Você usa a mesma técnica para atribuir cada um deles a variáveis ​​separadas.

Listagem de diretórios

O módulo glob é outra ferramenta da biblioteca padrão do Python. É uma maneira fácil de obter o conteúdo de um diretório programaticamente e usa o tipo de curinga com o qual você pode já estar familiarizado por trabalhar na linha de comando.

O módulo glob usa curingas semelhantes a shell.

>>> os.chdir('/Users/pilgrim/diveintopython3/')
>>> import glob
>>> glob.glob('examples/*.xml')                  ①
['examples\\feed-broken.xml',
 'examples\\feed-ns0.xml',
 'examples\\feed.xml']
>>> os.chdir('examples/')                        ②
>>> glob.glob('*test*.py')                       ③
['alphameticstest.py',
 'pluraltest1.py',
 'pluraltest2.py',
 'pluraltest3.py',
 'pluraltest4.py',
 'pluraltest5.py',
 'pluraltest6.py',
 'romantest1.py',
 'romantest10.py',
 'romantest2.py',
 'romantest3.py',
 'romantest4.py',
 'romantest5.py',
 'romantest6.py',
 'romantest7.py',
 'romantest8.py',
 'romantest9.py']
  1. O módulo glob recebe um curinga e retorna o caminho de todos os arquivos e diretórios que correspondem ao curinga. Neste exemplo, o curinga é um caminho de diretório mais “*.xml”, que corresponderá a todos os arquivos .xml no subdiretório examples.
  2. Agora mude o diretório de trabalho atual para o subdiretório examples. A função os.chdir() pode ter nomes de caminhos relativos.
  3. Você pode incluir vários curingas em seu padrão glob. Este exemplo encontra todos os arquivos no diretório de trabalho atual que terminam em uma extensão .py e contêm a palavra test em qualquer lugar em seu nome de arquivo.

Obtendo metadados de arquivo

Cada sistema de arquivos moderno armazena metadados sobre cada arquivo: data de criação, data da última modificação, tamanho do arquivo e assim por diante. Python fornece uma única API para acessar esses metadados. Você não precisa abrir o arquivo; tudo que você precisa é o nome do arquivo.

>>> import os
>>> print(os.getcwd())                 ①
c:\Users\pilgrim\diveintopython3\examples
>>> metadata = os.stat('feed.xml')     ②
>>> metadata.st_mtime                  ③
1247520344.9537716
>>> import time                        ④
>>> time.localtime(metadata.st_mtime)  ⑤
time.struct_time(tm_year=2009, tm_mon=7, tm_mday=13, tm_hour=17,
  tm_min=25, tm_sec=44, tm_wday=0, tm_yday=194, tm_isdst=1)
  1. O diretório de trabalho atual é a pasta examples.
  2. feed.xml é um arquivo na pasta examples. Chamar a função os.stat() retorna um objeto que contém vários tipos diferentes de metadados sobre o arquivo.
  3. st_mtime é a hora da modificação, mas está em um formato que não é muito útil. (Tecnicamente, é o número de segundos desde a Época, que é definido como o primeiro segundo de 1º de janeiro de 1970. Sério).
  4. O módulo time faz parte da biblioteca padrão do Python. Ele contém funções para converter entre diferentes representações de tempo, formatar valores de tempo em strings e mexer com fusos horários.
  5. A função time.localtime() converte um valor de tempo de segundos desde a época (da propriedade st_mtime retornada da função os.stat()) em uma estrutura mais útil de ano, mês, dia, hora, minuto, segundo e assim por diante. Este arquivo foi modificado pela última vez em 13 de julho de 2009, por volta das 17:25.
# continued from the previous example
>>> metadata.st_size                              ①
3070
>>> import humansize
>>> humansize.approximate_size(metadata.st_size)  ②
'3.0 KiB'
  1. A função os.stat() também retorna o tamanho de um arquivo, na propriedade st_size. O arquivo feed.xml tem 3070 bytes.
  2. Você pode passar a propriedade st_size para a função approximate_size().

Construindo Nomes de Caminho Absolutos

Na seção anterior, a função glob.glob() retornou uma lista de nomes de caminhos relativos. O primeiro exemplo tinha nomes de caminho como 'examples\feed.xml', e o segundo exemplo tinha nomes de caminho relativos ainda mais curtos como 'romantest1.py'. Contanto que você permaneça no mesmo diretório de trabalho atual, esses nomes de caminho relativos funcionarão para abrir arquivos ou obter metadados de arquivos. Mas se você quiser construir um caminho de acesso absoluto - ou seja, um que inclua todos os nomes de diretório de volta ao diretório raiz ou letra de unidade - então você precisará da função os.path.realpath().

>>> import os
>>> print(os.getcwd())
c:\Users\pilgrim\diveintopython3\examples
>>> print(os.path.realpath('feed.xml'))
c:\Users\pilgrim\diveintopython3\examples\feed.xml

Compreensões de lista

Você pode usar qualquer expressão Python em uma compreensão de lista.

Uma compreensão de lista fornece uma maneira compacta de mapear uma lista em outra lista, aplicando uma função a cada um dos elementos da lista.

>>> a_list = [1, 9, 8, 4]
>>> [elem * 2 for elem in a_list]           ①
[2, 18, 16, 8]
>>> a_list                                  ②
[1, 9, 8, 4]
>>> a_list = [elem * 2 for elem in a_list]  ③
>>> a_list
[2, 18, 16, 8]
  1. Para entender isso, olhe da direita para a esquerda. a_list é a lista que você está mapeando. O python intérpreta percorre a_list um elemento de cada vez, atribuir temporariamente o valor de cada elemento para a variável elem. O Python então aplica a operação elem * 2 e anexa esse resultado à lista retornada.
  2. Uma compreensão de lista cria uma nova lista; não altera a lista original.
  3. É seguro atribuir o resultado de uma compreensão de lista à variável que você está mapeando. O Python constrói a nova lista na memória e, quando a compreensão da lista é concluída, ele atribui o resultado à variável original.

Você pode usar qualquer expressão Python em uma compreensão de lista, incluindo as funções no módulo os para manipular arquivos e diretórios.

>>> import os, glob
>>> glob.glob('*.xml')                                 ①
['feed-broken.xml', 'feed-ns0.xml', 'feed.xml']
>>> [os.path.realpath(f) for f in glob.glob('*.xml')]  ②
['c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-broken.xml',
 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-ns0.xml',
 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed.xml']
  1. Isso retorna uma lista de todos os arquivos .xml no diretório de trabalho atual.
  2. Essa compreensão de lista pega essa lista de arquivos .xml e a transforma em uma lista de nomes de caminho completos.

As compreensões de lista também podem filtrar itens, produzindo um resultado que pode ser menor do que a lista original.

>>> import os, glob
>>> [f for f in glob.glob('*.py') if os.stat(f).st_size > 6000]  ①
['pluraltest6.py',
 'romantest10.py',
 'romantest6.py',
 'romantest7.py',
 'romantest8.py',
 'romantest9.py']
  1. Para filtrar uma lista, você pode incluir uma cláusula if no final da compreensão da lista. A expressão após a palavra-chave if será avaliada para cada item da lista. Se a expressão for avaliada como True, o item será incluído na saída. Essa compreensão de lista examina a lista de todos os arquivos .py no diretório atual e a instrução if filtra essa lista testando se o tamanho de cada arquivo é maior do que 6000 bytes. Existem seis desses arquivos, portanto, a compreensão da lista retorna uma lista de seis nomes de arquivo.

Todos os exemplos de compreensões de lista até agora apresentam expressões simples - multiplique um número por uma constante, chame uma única função ou simplesmente retorne o item original da lista (após a filtragem). Mas não há limite para o quão complexa pode ser a compreensão de uma lista.

>>> import os, glob
>>> [(os.stat(f).st_size, os.path.realpath(f)) for f in glob.glob('*.xml')]            ①
[(3074, 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-broken.xml'),
 (3386, 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-ns0.xml'),
 (3070, 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed.xml')]
>>> import humansize
>>> [(humansize.approximate_size(os.stat(f).st_size), f) for f in glob.glob('*.xml')]  ②
[('3.0 KiB', 'feed-broken.xml'),
 ('3.3 KiB', 'feed-ns0.xml'),
 ('3.0 KiB', 'feed.xml')]
  1. Esta compreensão de lista encontra todos os arquivos .xml no diretório de trabalho atual, obtém o tamanho de cada arquivo (chamando a função os.stat()) e constrói uma tupla do tamanho do arquivo e o caminho absoluto de cada arquivo (chamando a função os.path.realpath()).
  2. Essa compreensão se baseia na anterior para chamar a função approximate_size() com o tamanho do arquivo .xml de cada arquivo.

Compreensão do dicionário

Uma compreensão de dicionário é como uma compreensão de lista, mas constrói um dicionário em vez de uma lista.

>>> import os, glob
>>> metadata = [(f, os.stat(f)) for f in glob.glob('*test*.py')]    ①
>>> metadata[0]                                                     ②
('alphameticstest.py', nt.stat_result(st_mode=33206, st_ino=0, st_dev=0,
 st_nlink=0, st_uid=0, st_gid=0, st_size=2509, st_atime=1247520344,
 st_mtime=1247520344, st_ctime=1247520344))
>>> metadata_dict = {f:os.stat(f) for f in glob.glob('*test*.py')}  ③
>>> type(metadata_dict)                                             ④
<class 'dict'>
>>> list(metadata_dict.keys())                                      ⑤
['romantest8.py', 'pluraltest1.py', 'pluraltest2.py', 'pluraltest5.py',
 'pluraltest6.py', 'romantest7.py', 'romantest10.py', 'romantest4.py',
 'romantest9.py', 'pluraltest3.py', 'romantest1.py', 'romantest2.py',
 'romantest3.py', 'romantest5.py', 'romantest6.py', 'alphameticstest.py',
 'pluraltest4.py']
>>> metadata_dict['alphameticstest.py'].st_size                     ⑥
2509
  1. Esta não é uma compreensão de dicionário; é uma compreensão de lista. Ele encontra todos os arquivos .py com test em seus nomes e, em seguida, constrói uma tupla do nome do arquivo e dos metadados do arquivo (a partir da chamada da função os.stat()).
  2. Cada item da lista resultante é uma tupla.
  3. Esta é uma compreensão de dicionário. A sintaxe é semelhante à compreensão de uma lista, com duas diferenças. Primeiro, ele é colocado entre chaves em vez de colchetes. Em segundo lugar, em vez de uma única expressão para cada item, ele contém duas expressões separadas por dois pontos. A expressão antes dos dois pontos (f neste exemplo) é a chave do dicionário; a expressão após os dois pontos (os.stat(f) neste exemplo) é o valor.
  4. Uma compreensão de dicionário retorna um dicionário.
  5. As chaves deste dicionário específico são simplesmente os nomes de arquivo retornados da chamada para glob.glob('*test*.py').
  6. O valor associado a cada chave é o valor de retorno da função os.stat(). Isso significa que podemos “pesquisar” um arquivo por nome neste dicionário para obter seus metadados de arquivo. Uma das partes dos metadados é st_size o tamanho do arquivo. O arquivo alphameticstest.py tem 2509 bytes.

Como as compreensões de lista, você pode incluir uma cláusula if em uma compreensão de dicionário para filtrar a sequência de entrada com base em uma expressão que é avaliada com cada item.

>>> import os, glob, humansize
>>> metadata_dict = {f:os.stat(f) for f in glob.glob('*')}                                  ①
>>> humansize_dict = {os.path.splitext(f)[0]:humansize.approximate_size(meta.st_size) \     
...                   for f, meta in metadata_dict.items() if meta.st_size > 6000}          ②
>>> list(humansize_dict.keys())                                                             ③
['romantest9', 'romantest8', 'romantest7', 'romantest6', 'romantest10', 'pluraltest6']
>>> humansize_dict['romantest9']                                                            ④
'6.5 KiB'
  1. Esta compreensão de dicionário constrói uma lista de todos os arquivos no diretório de trabalho atual (glob.glob('*')), obtém os metadados de cada arquivo ( os.stat(f)) e constrói um dicionário cujas chaves são nomes de arquivos e cujos valores são os metadados de cada arquivo.
  2. Esta compreensão de dicionário se baseia na compreensão anterior, filtra arquivos menores que 6000 bytes (if meta.st_size > 6000) e usa essa lista filtrada para construir um dicionário cujas chaves são o nome do arquivo menos a extensão (os.path.splitext(f)[0]) e cujos valores são o tamanho aproximado de cada arquivo (humansize.approximate_size(meta.st_size)).
  3. Como você viu em um exemplo anterior, existem seis desses arquivos, portanto, existem seis itens neste dicionário.
  4. O valor de cada chave é a string retornada da função approximate_size().

Outras coisas divertidas para fazer com as compreensões de dicionário

Aqui está um truque com compreensões de dicionário que pode ser útil algum dia: trocar as chaves e os valores de um dicionário.

>>> a_dict = {'a': 1, 'b': 2, 'c': 3}
>>> {value:key for key, value in a_dict.items()}
{1: 'a', 2: 'b', 3: 'c'}

Claro, isso só funciona se os valores do dicionário forem imutáveis, como strings ou tuplas. Se você tentar fazer isso com um dicionário que contenha listas, a falha será espetacular.

>>> a_dict = {'a': [1, 2, 3], 'b': 4, 'c': 5}
>>> {value:key for key, value in a_dict.items()}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <dictcomp>
TypeError: unhashable type: 'list'

Definir Compreensões

Para não ficar de fora, os conjuntos também têm sua própria sintaxe de compreensão. É notavelmente semelhante à sintaxe para compreensões de dicionário. A única diferença é que os conjuntos têm apenas valores em vez de pares chave: valor.

>>> a_set = set(range(10))
>>> a_set
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
>>> {x ** 2 for x in a_set}           ①
{0, 1, 4, 81, 64, 9, 16, 49, 25, 36}
>>> {x for x in a_set if x % 2 == 0}  ②
{0, 8, 2, 4, 6}
>>> {2**x for x in range(10)}         ③
{32, 1, 2, 4, 8, 64, 128, 256, 16, 512}
  1. As compreensões de conjuntos podem ter um conjunto como entrada. Esta compreensão de conjunto calcula os quadrados do conjunto de números de 0 a 9.
  2. Como as compreensões de lista e de dicionário, as compreensões de conjunto podem conter uma cláusula if para filtrar cada item antes de retorná-lo no conjunto de resultados.
  3. As compreensões de conjunto não precisam ter um conjunto como entrada; eles podem tomar qualquer sequência.

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

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