Você está aqui: Home Mergulhar em Python 3

Nível de Dificuldade: ♦♦♢♢♢

Tipos de Dados Nativos

Wonder is the foundation of all philosophy, inquiry its progress, ignorance its end.
— Michel de Montaigne

 

Mergulhar

Tipos de dados. Vamos pôr lado o nosso primeiro primeiro programa em Python por um minuto, e falar de tipos de dados. Em Python, todos os valores têm um tipo de dados, mas não é preciso declarar o tipo das variáveis. Como funciona isto? Baseado na atribuição original de cada variável, o Python descobre qual o seu tipo e mantém esse registo guardado internamente.

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

  1. Booleanos são True ou False. (Verdadeiro ou Falso)
  2. Números podem ser inteiros (1 e 2), vírgula flutuante (1.1 e 1.2), fracções (1/2 e 2/3), ou até números complexos.
  3. Strings são sequências de caracteres Unicode, por exemplo, um documento HTML.
  4. Bytes e vectores de bytes, por exemplo, um ficheiro de imagem JPEG.
  5. Listas são sequências ordenadas de valores.
  6. Tuplos são sequências ordenadas e imutáveis de valores.
  7. Conjuntos são conjuntos de valores sem ordem.
  8. Dicionários são conjuntos sem ordem de pares chave-valor.

Claro que existem mais tipos que estes. Tudo é um objecto em Python, portanto há tipos como módulo, função, classe, método, ficheiro, e até código compilado. Já vimos alguns destes: módulos têm nome, funções têm docstrings, etc. Vamos aprender sobre classes em Classes e Iteradores, e sobre ficheiros em Ficheiros.

Strings e bytes são suficientemente importantes — e complicados o suficiente — que até têm o seu próprio capítulo. Vamos olhar para os outros primeiro.

Booleanos

Os booleanos são verdadeiro ou falso. O Python tem duas constantes, True e False, que podem ser usadas para atribuir valores booleanos directamente. Expressões também pode ser avaliadas para um valor booleano. Em certos sítos (como instruções if), o Python espera uma expressão que avalie para um valor booleano. Estes sítios são chamados contextos booleanos. Pode ser usada praticamente qualquer expressão num contexto booleano, e o Python tentará determinar a sua veracidade. Diferentes tipos de dados têm diferentes regras quanto a quais valores são verdadeiro ou falso num contexto booleano. (Isto fará mais sentido depois de ver exemplos concretos mais à frente neste capítulo)

Por exemplo, tirando este fragmento de humansize.py:

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

size é um inteiro, 0 é um inteiror, e < é um operador numérico. O resultado da expressão size < 0 é sempre um booleano. Pode testar isto na shell interactiva do Python:

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

Devido a alguns problemas herdados 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: int division or modulo by zero

Urghh! Não fazer isto. Esqueça que foi aqui mencionado.

Números

Os números são espetaculares. Há tantos de onde escolher. O Python suporta números inteiros e ponto flutuante. Não há nenhuma declaração de tipo para os distinguir; O Python diferencia-os pela presença do ponto decimal.

>>> type(1)                 
<class 'int'>
>>> isinstance(1, int)      
True
>>> 1 + 1                   
2
>>> 1 + 1.0                 
2.0
>>> type(2.0)
<class 'float'>
  1. Pode ser usado a função type() para verificar o tipo de qualquer valor ou variável. Como é de esperar, 1 é um int.
  2. Da mesma forma, pode ser usado a função isinstance() para verificar se um valor ou variável é de um determinado tipo.
  3. Adicionar um int a um int resulta num int.
  4. Adicionar um int a um float resulta num float. O Python converte o int num float para efectuar a adição, retornando um float como resultado.

Transformar Inteiros para Ponto Flutuante e Vice-Versa

Como foi visto, alguns operadores (como a adição) convertem inteiros para ponto flutuante conforme necessário. Também é possível fazer a conversão explícitamente.

>>> 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. É possível converter explícitamente um int para um float chamando a função float().
  2. Da mesma forma, é possível converter um 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 direcção a 0. É uma verdadeira função para truncar, não arredondar por defeito.
  5. Os números de ponto flutuante são precisos até 15 casas decimais.
  6. Os inteiros podem ser arbitrariamente longos.

O Python 2 tinha diferentes tipos para int e long. O tipo int estava limitado até sys.maxint, que variava entre plataformas, mas normalmente era 232-1. O Python 3 têm apenas um tipo inteiro, que se comporta como o antigo tipo long do Python 2. Ver PEP 237 para detalhes.

Operações Numéricas Comuns

É possível fazer todo o tipo de coisas 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 / efectua a divisão de ponto flutuante. Retorna um float mesmo que ambos o numerador e demoninador sejam ints.
  2. O operador // efectua um tipo peculiar de divisão inteira. Quanto o resultado é positivo, é equivalente a truncar para 0 casas decimais, mas é preciso ter cuidado com isto.
  3. Quando a divisão é de números negativos, o operador // arredonda por excesso para o inteiro mais próximo. Matemáticamente falando, arredonda para baixo visto que −6 é menor que −5, mas pode ser enganador se se tiver à espera de −5.
  4. O operador // nem sempre retorna um inteiro. Se o numerador ou o denominador for um float, irá arredondar na mesma para o inteiro mais próximo, mas o valor de retorno será um float.
  5. O operador ** efectua a expoenciação. 112 é 121.
  6. O operador % devolve o resto da divisão inteira. 11 a dividir por 2 é 5 com um resto de 1, portanto o resultado é 1.

No Python 2, o operador / normalmente significava divisão inteira, mas era possível comportar-se como divisão de ponto flutuante incluíndo uma directiva especial no código. No Python 3, o operador / significa sempre divisão de ponto flutuante. Ver PEP 238 para detalhes.

Fracções

O Python não está limitado a números inteiros e ponto flutuante. Também pode fazer todo o género de coisas que se aprende na escola para depois esquecer.

>>> 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 usar fracções, importar o módulo fractions.
  2. Para definir fracções, criar um objecto Fraction e passar o numerador e demoninador.
  3. É possível efectuar todas as operações matemáticas usuais com fracções. As operações retornam um novo objecto Fraction. 2 * (1/3) = (2/3)
  4. O objecto Fraction reduz as fracções automáticamente. (6/4) = (3/2)
  5. O Python têm o bom senso de não criar uma fracção com o denominador zero.

Trignometria

Também é possível fazer trignometria 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 têm a constante para π, o rácio da circunferência de um círculo para o diâmetro.
  2. O módulo math têm todas as funções trignométricas básicas, incluíndo sin(), cos(), tan(), e todas as variantes como asin().
  3. É de reparar, no entanto, que o Python não têm precisão infinita. tan(π / 4) deveria retornar 1.0, não 0.99999999999999989.

Números num contexto booleano

É possível usar númerosnum contexto booleano, tal como na instrução if. O valor zero é falso, e valores diferentes de zero são verdadeiros.

>>> def is_it_true(anything):             
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true(1)                         
yes, it's true
>>> is_it_true(-1)
yes, it's true
>>> is_it_true(0)
no, it's false
>>> is_it_true(0.1)                       
yes, it's true
>>> is_it_true(0.0)
no, it's false
>>> import fractions
>>> is_it_true(fractions.Fraction(1, 2))  
yes, it's true
>>> is_it_true(fractions.Fraction(0, 1))
no, it's false
  1. Sabia que é possível definir funções na shell interactiva do Python? Basta premir ENTER no fim de cada linha, e ENTER numa linha em branco para terminar.
  2. Num contexto booleano, valores diferentes de zero são verdade, e 0 é falso.
  3. Números de ponto flutuante diferentes de zero são verdade; 0.0 é falso. É preciso ter cuidado com isto! Se existir o menor erro de arrendamento (o que não é impossível, como foi visto na secção anterior) então o Python irá testar 0.0000000000001 em vez de 0 e irá retornar True.
  4. Fracções também podem ser usadas num contexto booleano. Fraction(0, n) é falso para todos os valores de n. Todas as restantes fracções são verdadeiras.

Listas

As listas em Python são o cavalo para todo o trabalho. Quando se fala em "lista", poderá levar a pensar em "vector cujo tamanho é preciso declarar com antecedência, que só pode conter elementos do mesmo tipo, etc". Não pense assim. As listas são muito mais fixes que isso.

Uma lista em Python é como um array em Perl 5. Em Perl 5, as variáves que guardam vectores começam sempre com o caracter @; em Python, as variáveis podem ter qualquer nome, e o Python mantém o registo do tipo de dados internamente.

Uma lista em Python é muito mais que um array em Java (embora possa ser usado como tal se isso for realmente tudo o que se quiser da vida). Uma melhor analogia seria para a classe ArrayList, que pode conter objectos arbitrários e expandir-se dinamicamente conforme sejam adicionados novos items.

Criar uma lista

Criar uma lista é fácil; usa-se parênteses rectos para envolver uma lista de valores separados por vírgula.

>>> a_list = ['a', 'b', 'mpilgrim', 'z', 'example']  
>>> a_list
['a', 'b', 'mpilgrim', 'z', 'example']
>>> a_list[0]                                        
'a'
>>> a_list[4]                                        
'example'
>>> a_list[-1]                                       
'example'
>>> a_list[-3]                                       
'mpilgrim'
  1. Primeiro, define-se uma lista de cinco items. Notar que é mantido a ordem original. Isto não é acidental. Uma lista é um conjunto ordenado de items.
  2. Uma lista pode ser usada como um vector com início em zero. O primeiro elemento não vazio de uma lista é sempre a_list[0]
  3. O último item desta lista é a_list[4], devido às listas terem sempre início em zero.
  4. Um índice negativo acede aos items desde o fim da lista contando para trás. O último item de uma lista não vazia é sempre a_list[-1].
  5. Se os índices negativos for confusos, basta pensar desta forma: a_list[-n] == a_list[len(a_list) - n]. Portanto nesta lista, a_list[-3] == a_list[5 - 3] == a_list[2].

Cortar Listas

Depois de ter definido uma lista, é possível obter qualquer parte como uma nova lista. Isto é chamado cortar a lista.

>>> a_list
['a', 'b', 'mpilgrim', 'z', 'example']
>>> a_list[1:3]            
['b', 'mpilgrim']
>>> a_list[1:-1]           
['b', 'mpilgrim', 'z']
>>> a_list[0:3]            
['a', 'b', 'mpilgrim']
>>> a_list[:3]             
['a', 'b', 'mpilgrim']
>>> a_list[3:]             
['z', 'example']
>>> a_list[:]              
['a', 'b', 'mpilgrim', 'z', 'example']
  1. É possível obter uma parte de uma lista, chamada uma fatia; espeficificando dois índices. O valor de retorno é uma nova lista que contém todos os elementos da lista, ordenados, começando pelo primeiro índice da fatia (neste caso a_lista[1], até mas não incluíndo o segundo índice da fatia (nesta caso a_list[3]).
  2. Cortar também funciona se um ou ambos os índices da fatia forem negativos. Se ajudar, pode-se pensar desta forma: lendo a lista da esquerda para a direita, o primeiro índice da fatia indica o primeiro item que se quer, e o segundo índice da fatia indica o primeiro item que não se quer. O valor de retorno é tudo o que estiver no meio.
  3. As listas têm início em zero, portanto a_list[0:3] retorna os primeiros três elementos da lista, começando em a_list[0], até mas não incluíndo a_list[3].
  4. Se o índice esquerdo da fatia for 0, pode ser deixado de fora, e o 0 fica implícito. Portanto a_list[:3] é o mesmo que a_list[0:3], porque o início em 0 está implícito.
  5. Da mesma forma, se o índice direito da fatia for o comprimento da lista, pode ser deixado de fora. Portanto a_list[3:] é o mesmo que a_list[3:5], porque a lista têm cinco items. Existe uma agradável simetria aqui. Nesta lista de cinco items, a_list[:3] retorna os primeiros 3 items, e a_list[3:] retorna os últimos dois items. De facto, a_list[:n] irá sempre retornar os primeiros n items, e a_list[n:] irá sempre retornar o resto, qualquer que seja o comprimento da lista.
  6. Se ambos os índices da fatia não forem indicados, todos os items da lista estão incluídos. Mas isto não é o mesmo que a variável original a_list. É uma nova lista que por acaso têm todos os mesmos items. a_list[:] é um atalho para criar uma cópia completa de uma lista.

Adicionar items a uma Lista

Existem quatro formas de adicionar items a uma lista.

>>> a_list = ['a']
>>> a_list = a_list + [2.0, 3]    
>>> a_list                        
['a', 2.0, 3]
>>> a_list.append(True)           
>>> a_list
['a', 2.0, 3, True]
>>> a_list.extend(['four', 'Ω'])  
>>> a_list
['a', 2.0, 3, True, 'four', 'Ω']
>>> a_list.insert(0, 'Ω')         
>>> a_list
['Ω', 'a', 2.0, 3, True, 'four', 'Ω']
  1. O operador + concatena listas para criar uma nova lista. Uma lista pode conter qualquer número de items; não há limite de tamanho (além da memória disponível). No entanto, se a memória for uma preocupação, é preciso reparar que a concatenação de listas cria uma segunda lista em memória. Neste caso, essa nova lista é imediatamente atribuída à variável existente a_list. Portanto esta linha de código é na realidade um processo de duas etapas — concatenação e depois atribuição — que pode (temporáriamente) consumir muita memória se estiver a lidar com listas grandes.
  2. Uma lista pode conter items de qualquer tipo de dados, e os items de uma lista não precisam de ser todos do mesmo tipo. Aqui têm-se uma lista contendo uma string, um número de vírgula flutuante, e um inteiro.
  3. O método append() adiciona um único item ao fim da lista. (Agora têm-se quatro tipos de dados diferentes na lista!)
  4. As listas são implementadas como classes. “Criar” uma lista é na realidade instanciar uma classe. E portanto, uma lista têm métodos para nela operar. O método extend() recebe um argumento, uma lista, e acrescenta cada um dos items do argumento à lista original.
  5. O método insert() insere um único item numa lista. O primeiro argumento é o índice do primeiro item na lista que irá mudar de posição. Os items de uma lista não precisam de ser únicos; por exemplo, existem agora dois diferentes items com o valor 'Ω': o primeiro item, a_list[0], e o último item, a_list[6].

a_list.insert(0, value) é como a função unshift() no Perl. Adiciona um item ao início da lista, e todos os restantes items ficam o seu índice incrementado para poder criar espaço.

Let’s look closer at the difference between append() and extend().

Aqui vê-se em maior detalhe a diferença entre append() e extend().

>>> a_list = ['a', 'b', 'c']
>>> a_list.extend(['d', 'e', 'f'])  
>>> a_list
['a', 'b', 'c', 'd', 'e', 'f']
>>> len(a_list)                     
6
>>> a_list[-1]
'f'
>>> a_list.append(['g', 'h', 'i'])  
>>> a_list
['a', 'b', 'c', 'd', 'e', 'f', ['g', 'h', 'i']]
>>> len(a_list)                     
7
>>> a_list[-1]
['g', 'h', 'i']
  1. O método extend() recebe um único argumento que é sempre uma lista, e adiciona cada um dos items dessa lista a a_list.
  2. Se uma lista tiver três items e for extendida com outra lista de três items, fica-se com uma lista com seis items.
  3. Por outro lado, o método append() recebe um único argumento, que pode ser qualquer tipo de dados. Aqui, está a chamar-se o método append() com uma lista de três elementos.
  4. Se uma lista tiver seis items e lhe for acresentada outra lista, acaba-se com... uma lista com sete items. Porquê sete? Porque o ultimo item (que foi mesmo agora acrescentado) é ele próprio uma lista. As listas podem conter qualquer tipo de dados, incluíndo outras listas. Isso pode ser o que se quer, ou talvez não. mas é o que se pediu, e foi o que se recebeu.

Searching For Values In A List

>>> a_list = ['a', 'b', 'new', 'mpilgrim', 'new']
>>> a_list.count('new')       
2
>>> 'new' in a_list           
True
>>> 'c' in a_list
False
>>> a_list.index('mpilgrim')  
3
>>> a_list.index('new')       
2
>>> a_list.index('c')         
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
ValueError: list.index(x): x not in list
  1. As you might expect, the count() method returns the number of occurrences of a specific value in a list.
  2. If all you want to know is whether a value is in the list or not, the in operator is slightly faster than using the count() method. The in operator always returns True or False; it will not tell you how many times the value appears in the list.
  3. Neither the in operator nor the count() method will tell you where in the list a value appears. If you need to know where in the list a value is, call the index() method. By default it will search the entire list, although you can specify an optional second argument of the (0-based) index to start from, and even an optional third argument of the (0-based) index to stop searching.
  4. The index() method finds the first occurrence of a value in the list. In this case, 'new' occurs twice in the list, in a_list[2] and a_list[4], but the index() method will return only the index of the first occurrence.
  5. As you might not expect, if the value is not found in the list, the index() method will raise an exception.

Wait, what? That’s right: the index() method raises an exception if it doesn’t find the value in the list. This is notably different from most languages, which will return some invalid index (like -1). While this may seem annoying at first, I think you will come to appreciate it. It means your program will crash at the source of the problem instead of failing strangely and silently later. Remember, -1 is a valid list index. If the index() method returned -1, that could lead to some not-so-fun debugging sessions!

Removing Items From A List

Lists can expand and contract automatically. You’ve seen the expansion part. There are several different ways to remove items from a list as well.

>>> a_list = ['a', 'b', 'new', 'mpilgrim', 'new']
>>> a_list[1]
'b'
>>> del a_list[1]         
>>> a_list
['a', 'new', 'mpilgrim', 'new']
>>> a_list[1]             
'new'
  1. You can use the del statement to delete a specific item from a list.
  2. Accessing index 1 after deleting index 1 does not result in an error. All items after the deleted item shift their positional index to “fill the gap” created by deleting the item.

Don’t know the positional index? Not a problem; you can remove items by value instead.

>>> a_list.remove('new')  
>>> a_list
['a', 'mpilgrim', 'new']
>>> a_list.remove('new')  
>>> a_list
['a', 'mpilgrim']
>>> a_list.remove('new')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
  1. You can also remove an item from a list with the remove() method. The remove() method takes a value and removes the first occurrence of that value from the list. Again, all items after the deleted item will have their positional indices bumped down to “fill the gap.” Lists never have gaps.
  2. You can call the remove() method as often as you like, but it will raise an exception if you try to remove a value that isn’t in the list.

Removing Items From A List: Bonus Round

Another interesting list method is pop(). The pop() method is yet another way to remove items from a list, but with a twist.

>>> a_list = ['a', 'b', 'new', 'mpilgrim']
>>> a_list.pop()   
'mpilgrim'
>>> a_list
['a', 'b', 'new']
>>> a_list.pop(1)  
'b'
>>> a_list
['a', 'new']
>>> a_list.pop()
'new'
>>> a_list.pop()
'a'
>>> a_list.pop()   
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: pop from empty list
  1. When called without arguments, the pop() list method removes the last item in the list and returns the value it removed.
  2. You can pop arbitrary items from a list. Just pass a positional index to the pop() method. It will remove that item, shift all the items after it to “fill the gap,” and return the value it removed.
  3. Calling pop() on an empty list raises an exception.

Calling the pop() list method without an argument is like the pop() function in Perl. It removes the last item from the list and returns the value of the removed item. Perl has another function, shift(), which removes the first item and returns its value; in Python, this is equivalent to a_list.pop(0).

Lists In A Boolean Context

You can also use a list in a boolean context, such as an if statement.

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true([])             
no, it's false
>>> is_it_true(['a'])          
yes, it's true
>>> is_it_true([False])        
yes, it's true
  1. In a boolean context, an empty list is false.
  2. Any list with at least one item is true.
  3. Any list with at least one item is true. The value of the items is irrelevant.

Tuples

A tuple is an immutable list. A tuple can not be changed in any way once it is created.

>>> 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. A tuple is defined in the same way as a list, except that the whole set of elements is enclosed in parentheses instead of square brackets.
  2. The elements of a tuple have a defined order, just like a list. Tuple indices are zero-based, just like a list, so the first element of a non-empty tuple is always a_tuple[0].
  3. Negative indices count from the end of the tuple, just like a list.
  4. Slicing works too, just like a list. When you slice a list, you get a new list; when you slice a tuple, you get a new tuple.

The major difference between tuples and lists is that tuples can not be changed. In technical terms, tuples are immutable. In practical terms, they have no methods that would allow you to change them. Lists have methods like append(), extend(), insert(), remove(), and pop(). Tuples have none of these methods. You can slice a tuple (because that creates a new tuple), and you can check whether a tuple contains a particular value (because that doesn’t change the tuple), and… that’s about it.

# continued from the previous example
>>> 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. You can’t add elements to a tuple. Tuples have no append() or extend() method.
  2. You can’t remove elements from a tuple. Tuples have no remove() or pop() method.
  3. You can find elements in a tuple, since this doesn’t change the tuple.
  4. You can also use the in operator to check if an element exists in the tuple.

So what are tuples good for?

Tuples can be converted into lists, and vice-versa. The built-in tuple() function takes a list and returns a tuple with the same elements, and the list() function takes a tuple and returns a list. In effect, tuple() freezes a list, and list() thaws a tuple.

Tuples In A Boolean Context

You can use tuples in a boolean context, such as an if statement.

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true(())             
no, it's false
>>> is_it_true(('a', 'b'))     
yes, it's true
>>> is_it_true((False,))       
yes, it's true
>>> type((False))              
<class 'bool'>
>>> type((False,))
<class 'tuple'>
  1. In a boolean context, an empty tuple is false.
  2. Any tuple with at least one item is true.
  3. Any tuple with at least one item is true. The value of the items is irrelevant. But what’s that comma doing there?
  4. To create a tuple of one item, you need a comma after the value. Without the comma, Python just assumes you have an extra pair of parentheses, which is harmless, but it doesn’t create a tuple.

Assigning Multiple Values At Once

Here’s a cool programming shortcut: in Python, you can use a tuple to assign multiple values at once.

>>> v = ('a', 2, True)
>>> (x, y, z) = v       
>>> x
'a'
>>> y
2
>>> z
True
  1. v is a tuple of three elements, and (x, y, z) is a tuple of three variables. Assigning one to the other assigns each of the values of v to each of the variables, in order.

This has all kinds of uses. Suppose you want to assign names to a range of values. You can use the built-in range() function with multi-variable assignment to quickly assign consecutive values.

>>> (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)  
>>> MONDAY                                                                       
0
>>> TUESDAY
1
>>> SUNDAY
6
  1. The built-in range() function constructs a sequence of integers. (Technically, the range() function returns an iterator, not a list or a tuple, but you’ll learn about that distinction later.) MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, and SUNDAY are the variables you’re defining. (This example came from the calendar module, a fun little module that prints calendars, like the UNIX program cal. The calendar module defines integer constants for days of the week.)
  2. Now each variable has its value: MONDAY is 0, TUESDAY is 1, and so forth.

You can also use multi-variable assignment to build functions that return multiple values, simply by returning a tuple of all the values. The caller can treat it as a single tuple, or it can assign the values to individual variables. Many standard Python libraries do this, including the os module, which you'll learn about in the next chapter.

Sets

A set is an unordered “bag” of unique values. A single set can contain values of any immutable datatype. Once you have two sets, you can do standard set operations like union, intersection, and set difference.

Creating A Set

First things first. Creating a set is easy.

>>> a_set = {1}     
>>> a_set
{1}
>>> type(a_set)     
<class 'set'>
>>> a_set = {1, 2}  
>>> a_set
{1, 2}
  1. To create a set with one value, put the value in curly brackets ({}).
  2. Sets are actually implemented as classes, but don’t worry about that for now.
  3. To create a set with multiple values, separate the values with commas and wrap it all up with curly brackets.

You can also create a set out of a list.

>>> a_list = ['a', 'b', 'mpilgrim', True, False, 42]
>>> a_set = set(a_list)                           
>>> a_set                                         
{'a', False, 'b', True, 'mpilgrim', 42}
>>> a_list                                        
['a', 'b', 'mpilgrim', True, False, 42]
  1. To create a set from a list, use the set() function. (Pedants who know about how sets are implemented will point out that this is not really calling a function, but instantiating a class. I promise you will learn the difference later in this book. For now, just know that set() acts like a function, and it returns a set.)
  2. As I mentioned earlier, a single set can contain values of any datatype. And, as I mentioned earlier, sets are unordered. This set does not remember the original order of the list that was used to create it. If you were to add items to this set, it would not remember the order in which you added them.
  3. The original list is unchanged.

Don’t have any values yet? Not a problem. You can create an empty set.

>>> a_set = set()    
>>> a_set            
set()
>>> type(a_set)      
<class 'set'>
>>> len(a_set)       
0
>>> not_sure = {}    
>>> type(not_sure)
<class 'dict'>
  1. To create an empty set, call set() with no arguments.
  2. The printed representation of an empty set looks a bit strange. Were you expecting {}, perhaps? That would denote an empty dictionary, not an empty set. You’ll learn about dictionaries later in this chapter.
  3. Despite the strange printed representation, this is a set…
  4. …and this set has no members.
  5. Due to historical quirks carried over from Python 2, you can not create an empty set with two curly brackets. This actually creates an empty dictionary, not an empty set.

Modifying A Set

There are two different ways to add values to an existing set: the add() method, and the update() method.

>>> a_set = {1, 2}
>>> a_set.add(4)  
>>> a_set
{1, 2, 4}
>>> len(a_set)    
3
>>> a_set.add(1)  
>>> a_set
{1, 2, 4}
>>> len(a_set)    
3
  1. The add() method takes a single argument, which can be any datatype, and adds the given value to the set.
  2. This set now has 3 members.
  3. Sets are bags of unique values. If you try to add a value that already exists in the set, it will do nothing. It won’t raise an error; it’s just a no-op.
  4. This set still has 3 members.
>>> a_set = {1, 2, 3}
>>> a_set
{1, 2, 3}
>>> a_set.update({2, 4, 6})                       
>>> a_set                                         
{1, 2, 3, 4, 6}
>>> a_set.update({3, 6, 9}, {1, 2, 3, 5, 8, 13})  
>>> a_set
{1, 2, 3, 4, 5, 6, 8, 9, 13}
>>> a_set.update([10, 20, 30])                    
>>> a_set
{1, 2, 3, 4, 5, 6, 8, 9, 10, 13, 20, 30}
  1. The update() method takes one argument, a set, and adds all its members to the original set. It’s as if you called the add() method with each member of the set.
  2. Duplicate values are ignored, since sets can not contain duplicates.
  3. You can actually call the update() method with any number of arguments. When called with two sets, the update() method adds all the members of each set to the original set (dropping duplicates).
  4. The update() method can take objects of a number of different datatypes, including lists. When called with a list, the update() method adds all the items of the list to the original set.

Removing Items From A Set

There are three ways to remove individual values from a set. The first two, discard() and remove(), have one subtle difference.

>>> a_set = {1, 3, 6, 10, 15, 21, 28, 36, 45}
>>> a_set
{1, 3, 36, 6, 10, 45, 15, 21, 28}
>>> a_set.discard(10)                        
>>> a_set
{1, 3, 36, 6, 45, 15, 21, 28}
>>> a_set.discard(10)                        
>>> a_set
{1, 3, 36, 6, 45, 15, 21, 28}
>>> a_set.remove(21)                         
>>> a_set
{1, 3, 36, 6, 45, 15, 28}
>>> a_set.remove(21)                         
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 21
  1. The discard() method takes a single value as an argument and removes that value from the set.
  2. If you call the discard() method with a value that doesn’t exist in the set, it does nothing. No error; it’s just a no-op.
  3. The remove() method also takes a single value as an argument, and it also removes that value from the set.
  4. Here’s the difference: if the value doesn’t exist in the set, the remove() method raises a KeyError exception.

Like lists, sets have a pop() method.

>>> a_set = {1, 3, 6, 10, 15, 21, 28, 36, 45}
>>> a_set.pop()                                
1
>>> a_set.pop()
3
>>> a_set.pop()
36
>>> a_set
{6, 10, 45, 15, 21, 28}
>>> a_set.clear()                              
>>> a_set
set()
>>> a_set.pop()                                
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'pop from an empty set'
  1. The pop() method removes a single value from a set and returns the value. However, since sets are unordered, there is no “last” value in a set, so there is no way to control which value gets removed. It is completely arbitrary.
  2. The clear() method removes all values from a set, leaving you with an empty set. This is equivalent to a_set = set(), which would create a new empty set and overwrite the previous value of the a_set variable.
  3. Attempting to pop a value from an empty set will raise a KeyError exception.

Common Set Operations

Python’s set type supports several common set operations.

>>> a_set = {2, 4, 5, 9, 12, 21, 30, 51, 76, 127, 195}
>>> 30 in a_set                                                     
True
>>> 31 in a_set
False
>>> b_set = {1, 2, 3, 5, 6, 8, 9, 12, 15, 17, 18, 21}
>>> a_set.union(b_set)                                              
{1, 2, 195, 4, 5, 6, 8, 12, 76, 15, 17, 18, 3, 21, 30, 51, 9, 127}
>>> a_set.intersection(b_set)                                       
{9, 2, 12, 5, 21}
>>> a_set.difference(b_set)                                         
{195, 4, 76, 51, 30, 127}
>>> a_set.symmetric_difference(b_set)                               
{1, 3, 4, 6, 8, 76, 15, 17, 18, 195, 127, 30, 51}
  1. To test whether a value is a member of a set, use the in operator. This works the same as lists.
  2. The union() method returns a new set containing all the elements that are in either set.
  3. The intersection() method returns a new set containing all the elements that are in both sets.
  4. The difference() method returns a new set containing all the elements that are in a_set but not b_set.
  5. The symmetric_difference() method returns a new set containing all the elements that are in exactly one of the sets.

Three of these methods are symmetric.

# continued from the previous example
>>> b_set.symmetric_difference(a_set)                                       
{3, 1, 195, 4, 6, 8, 76, 15, 17, 18, 51, 30, 127}
>>> b_set.symmetric_difference(a_set) == a_set.symmetric_difference(b_set)  
True
>>> b_set.union(a_set) == a_set.union(b_set)                                
True
>>> b_set.intersection(a_set) == a_set.intersection(b_set)                  
True
>>> b_set.difference(a_set) == a_set.difference(b_set)                      
False
  1. The symmetric difference of a_set from b_set looks different than the symmetric difference of b_set from a_set, but remember, sets are unordered. Any two sets that contain all the same values (with none left over) are considered equal.
  2. And that’s exactly what happens here. Don’t be fooled by the Python Shell’s printed representation of these sets. They contain the same values, so they are equal.
  3. The union of two sets is also symmetric.
  4. The intersection of two sets is also symmetric.
  5. The difference of two sets is not symmetric. That makes sense; it’s analogous to subtracting one number from another. The order of the operands matters.

Finally, there are a few questions you can ask of sets.

>>> a_set = {1, 2, 3}
>>> b_set = {1, 2, 3, 4}
>>> a_set.issubset(b_set)    
True
>>> b_set.issuperset(a_set)  
True
>>> a_set.add(5)             
>>> a_set.issubset(b_set)
False
>>> b_set.issuperset(a_set)
False
  1. a_set is a subset of b_set — all the members of a_set are also members of b_set.
  2. Asking the same question in reverse, b_set is a superset of a_set, because all the members of a_set are also members of b_set.
  3. As soon as you add a value to a_set that is not in b_set, both tests return False.

Sets In A Boolean Context

You can use sets in a boolean context, such as an if statement.

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true(set())          
no, it's false
>>> is_it_true({'a'})          
yes, it's true
>>> is_it_true({False})        
yes, it's true
  1. In a boolean context, an empty set is false.
  2. Any set with at least one item is true.
  3. Any set with at least one item is true. The value of the items is irrelevant.

Dictionaries

A dictionary is an unordered set of key-value pairs. When you add a key to a dictionary, you must also add a value for that key. (You can always change the value later.) Python dictionaries are optimized for retrieving the value when you know the key, but not the other way around.

A dictionary in Python is like a hash in Perl 5. In Perl 5, variables that store hashes always start with a % character. In Python, variables can be named anything, and Python keeps track of the datatype internally.

Creating A Dictionary

Creating a dictionary is easy. The syntax is similar to sets, but instead of values, you have key-value pairs. Once you have a dictionary, you can look up values by their key.

>>> a_dict = {'server': 'db.diveintopython3.org', 'database': 'mysql'}  
>>> a_dict
{'server': 'db.diveintopython3.org', 'database': 'mysql'}
>>> a_dict['server']                                                    
'db.diveintopython3.org'
>>> a_dict['database']                                                  
'mysql'
>>> a_dict['db.diveintopython3.org']                                    
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'db.diveintopython3.org'
  1. First, you create a new dictionary with two items and assign it to the variable a_dict. Each item is a key-value pair, and the whole set of items is enclosed in curly braces.
  2. 'server' is a key, and its associated value, referenced by a_dict['server'], is 'db.diveintopython3.org'.
  3. 'database' is a key, and its associated value, referenced by a_dict['database'], is 'mysql'.
  4. You can get values by key, but you can’t get keys by value. So a_dict['server'] is 'db.diveintopython3.org', but a_dict['db.diveintopython3.org'] raises an exception, because 'db.diveintopython3.org' is not a key.

Modifying A Dictionary

Dictionaries do not have any predefined size limit. You can add new key-value pairs to a dictionary at any time, or you can modify the value of an existing key. Continuing from the previous example:

>>> a_dict
{'server': 'db.diveintopython3.org', 'database': 'mysql'}
>>> a_dict['database'] = 'blog'  
>>> a_dict
{'server': 'db.diveintopython3.org', 'database': 'blog'}
>>> a_dict['user'] = 'mark'      
>>> a_dict                       
{'server': 'db.diveintopython3.org', 'user': 'mark', 'database': 'blog'}
>>> a_dict['user'] = 'dora'      
>>> a_dict
{'server': 'db.diveintopython3.org', 'user': 'dora', 'database': 'blog'}
>>> a_dict['User'] = 'mark'      
>>> a_dict
{'User': 'mark', 'server': 'db.diveintopython3.org', 'user': 'dora', 'database': 'blog'}
  1. You can not have duplicate keys in a dictionary. Assigning a value to an existing key will wipe out the old value.
  2. You can add new key-value pairs at any time. This syntax is identical to modifying existing values.
  3. The new dictionary item (key 'user', value 'mark') appears to be in the middle. In fact, it was just a coincidence that the items appeared to be in order in the first example; it is just as much a coincidence that they appear to be out of order now.
  4. Assigning a value to an existing dictionary key simply replaces the old value with the new one.
  5. Will this change the value of the user key back to "mark"? No! Look at the key closely — that’s a capital U in "User". Dictionary keys are case-sensitive, so this statement is creating a new key-value pair, not overwriting an existing one. It may look similar to you, but as far as Python is concerned, it’s completely different.

Mixed-Value Dictionaries

Dictionaries aren’t just for strings. Dictionary values can be any datatype, including integers, booleans, arbitrary objects, or even other dictionaries. And within a single dictionary, the values don’t all need to be the same type; you can mix and match as needed. Dictionary keys are more restricted, but they can be strings, integers, and a few other types. You can also mix and match key datatypes within a dictionary.

In fact, you’ve already seen a dictionary with non-string keys and values, in your first Python program.

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

Let's tear that apart in the interactive shell.

>>> 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. Like lists and sets, the len() function gives you the number of keys in a dictionary.
  2. And like lists and sets, you can use the in operator to test whether a specific key is defined in a dictionary.
  3. 1000 is a key in the SUFFIXES dictionary; its value is a list of eight items (eight strings, to be precise).
  4. Similarly, 1024 is a key in the SUFFIXES dictionary; its value is also a list of eight items.
  5. Since SUFFIXES[1000] is a list, you can address individual items in the list by their 0-based index.

Dictionaries In A Boolean Context

You can also use a dictionary in a boolean context, such as an if statement.

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true({})             
no, it's false
>>> is_it_true({'a': 1})       
yes, it's true
  1. In a boolean context, an empty dictionary is false.
  2. Any dictionary with at least one key-value pair is true.

None

None is a special constant in Python. It is a null value. None is not the same as False. None is not 0. None is not an empty string. Comparing None to anything other than None will always return False.

None is the only null value. It has its own datatype (NoneType). You can assign None to any variable, but you can not create other NoneType objects. All variables whose value is None are equal to each other.

>>> 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 In A Boolean Context

In a boolean context, None is false and not None is true.

>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true(None)
no, it's false
>>> is_it_true(not None)
yes, it's true

Further Reading

© 2001–11 Mark Pilgrim