bits

Transcrição

bits
Computadores e Programação
2003-2004
José António Paixão (T), F4
Fernando Nogueira (P1), D19.D
António José Silva (P2), D19.D
Página Web da disciplina:
http://argus.fis.uc.pt/cp
Ver também:
http://www.fis.uc.pt
link INSITU
Programa
2003-2004
• O modelo de von Neumann do computador digital. Arquitectura de um
computador moderno.
• Representação digital de dados. Códigos binários para representação de
inteiros (código de valor absoluto e sinal e código de complementos de
2), reais (vı́rgula flutuante), caracteres (ASCII, unicode), imagem
(RGB, JPEG) e som (CD-AUDIO).
• Operações numéricas sobre dados binários. Problemas ligados à
imprecisão da representação dos números reais em vı́rgula flutuante.
• Processadores. Funcionamento de um CPU. Representação binária do
código executável de um programa.
• Assembladores, compiladores e interpretadores. Linguagens de
programação de alto nı́vel e de muito alto nı́vel (VHLL). Sistemas
operativos.
• Introdução à linguagem de programação Python.
• A instrução de atribuição. Aliasing. Noção de ponteiro.
• Tipos numéricos: inteiros, inteiros longos, números em vı́rgula
flutuante e complexos.
• Sequências (listas, tuplas e sequências de caracteres). Iteração sobre
sequências e operações de fatiagem (slicing). Abrangências.
Dicionários.
• Instruções de controlo de fluxo: if..elif..else, while..else,
for..else.
• Funções. Espaço dos nomes e regras de alcance. Mecanismo de
passagem de argumentos e devolução de valores.
• Programação funcional e imperativa. Funções puras. As ferramentas de
programação funcional lambda, map, filter e reduce. Exemplos
de pequenos programas em estilo funcional.
• Módulos. Ferramentas de introspecção e metaprogramação.
• Ficheiros. Formatação. Redirecção dos canais de fluxo de entrada e
saı́da.
• Programação orientada por objectos. Noção de classe e instâncias de
classe. Atributos e métodos. Herança, encapsulamento e polimorfismo.
Sobrecarga de operadores. Objectos persistentes: módulos pickle e
shelve.
• Excepções. As instruções raise e try..except..finally.
• Recursão. Iteradores e geradores.
• Aplicações em Análise Numérica e a problemas de Fı́sica.
– Resolução de equações não-lineares: método da bissecção,
iteradores de ponto fixo, Regula-Falsi e Newton-Raphson.
– Interpolação de Lagrange.
– Ajuste de curvas a dados experimentais pelo método dos mı́nimos
quadrados.
– Derivação e integração numérica: método do trapézio, regra de
Simpson. Mı́nimos de curvas de energia potencial intramoleculares,
momentos de inércia, etc.
– Resolução de equações diferenciais ordinárias: método de Euler,
Heun (preditor-corrector), Runge-Kutta e Runge-Kutta-Fehlberg
(de passo adaptado). Equações do decaimento radioactivo,
osciladores, projécteis, movimento planetário, etc.
– Monte-Carlo.
Avaliação
A avaliação da disciplina é feita por frequência (opcional) e por
exame. Uma classificação de frequência igual ou superior a 10
valores concede dispensa de exame.
Os exames e a frequência têm ambos uma parte teórica e uma parte
prática com a cotação de 12 e 8 valores, respectivamente. A parte
teórica contém uma série de 20 questões de escolha múltipla e 2 ou 3
questões de desenvolvimento. A parte prática consiste na resolução
de problemas (algoritmo e codificação do programa frente ao
computador). A parte prática da frequência desenrola-se em duas
sessões (3 + 3 problemas, 2 * 4 valores). Nos exames a parte prática é
uma sessão única com 3 problemas.
É garantida a nota da parte prática de frequência se for igual ou
superior a 4 valores.
Na parte prática é permitida a consulta a todo o material de apoio. A
parte teórica é sem consulta.
O computador digital
Computador digital: sistema digital programável que permite
armazenar e processar informação em formato digital a velocidades
elevadas, realizando operações aritméticas e lógicas elementares.
Programa: sequência de instruções que processam os dados.
Quer os programas quer os dados são armazenados na memória do
computador em formato digital usando um código binário.
O 1◦ computador digital (ABC) foi construı́do em 1939
(Atanasoff/Berry). 1944 (MARK I, modelo de von Neumann):
velocidade de processamento: 0.00083 MIPS.
O EDVAC (1952) foi um dos primeiros computadores electrónicos e
ocupava a superfı́cie de um campo de futebol.
Modelo de von Neumann
O computador é constituı́do pelas seguintes unidades funcionais:
• Memória central
• Unidade aritmética e lógica
• Unidade de controlo
• Unidades de entrada e saı́da
Hoje em dia o processador engloba a unidade aritmética e lógica e a
unidade de controlo.
Modelo de von Neumann
Unidade
de
controlo
C
C
I
C
Unidade
A/L
D
Unidades
de
E/S
R
D
R
D
R
´
Memoria
Central
O processador não efectua operações directamente sobre a memória
(à excepção da transferência de dados).
O processamento é feito em células especiais de memória no interior
da UAL denominadas registos.
A tranferência de dados entre a memória central e os dipositivos de
entrada e saı́da pode ser feita passando pelo processador ou através de
acesso directo à memória (DMA).
Informação digital
• Os computadores processam informação digital
• Grandeza analógica: varia de forma contı́nua (ex: temperatura
lida num termómetro de Hg)
• Grandeza digital: descontı́nua (existe apenas um número finito de
estados).
Elemento mı́nimo de informação: dı́gito binário (bit).
bit: 2 estados 0 ou 1
Um conjunto de n bits pode tomar 2n configurações distintos,
podendo representar 2n objectos ou informações elementares.
Exemplo com 3 bits:
Configurações possı́veis (8 = 23 ): 000, 001, 010, 011,
100, 101, 110, 111.
Podemos utilizar estas combinações de bits para representar de forma
unı́voca 8 letras do alfabeto:
A
000
E
100
B
001
F
101
C
010
G
110
D
011
H
111
Codificação/descodificação
• Os computadores processam (apenas) informação/dados em
formato digital.
Todos os dados têm que ser codificados num formato digital para
serem passı́veis de tratamento informático.
Para cada tipo de dado tem de se estabelecer um código que permita
atribuir uma configuração binária única a cada dado desse tipo →
codificação. A operação inversa é a descodificação.
Vamos passar em revista alguns dos códigos actualmente usados para
representação digital de vários tipos de dados num computador:
• Números inteiros
• Números inteiros relativos
• Números reais
• Caracteres
• Som
• Imagem
• etc,...
Codificação de inteiros
A forma mais simples de representar um número inteiro num formato
digital corresponde a utilizar a sua representação binária, isto é, na
base 2.
O número é representado por n bits
an−1 an−2 · · · a0
onde ai = 0, 1 e i = 0, ..., n − 1.
O inteiro correspondente a esta sequência de bits é:
an−1 2
n−1
+ an−2 2
n−2
. . . + a0 =
n−1
∑ ai 2i
i=0
Este código designa-se por código binário ponderado.
O maior número inteiro que é possı́vel representar com um conjuto de
n bits é
n−1
∑ 2n = 2 n − 1
i=0
n bits → 2n números inteiros, incluindo o zero: 0, 1, . . ., 2n−1 .
Conversão decimal/binário
A conversão de um número inteiro na base 10 em código binário
ponderado é muito fácil.
Exemplo: Conversão do decimal 203 para a base 2. Representando o
quociente e o resto da divisão inteira pelos operadores div e mod,
temos:
203
101
50
25
12
6
3
1
div
div
div
div
div
div
div
div
2
2
2
2
2
2
2
2
=101; 203 mod 2 = 1 = a0;
= 50; 101 mod 2 = 1 = a1;
= 25; 50 mod 2 = 0 = a2;
= 12; 25 mod 2 = 1 = a3;
= 6;
12 mod 2 = 0 = a4;
= 3; 6 mod 2 = 0 = a5;
= 1; 3 mod 2 = 1 = a6;
= 0; 1 mod 2 = 1 = a7;
Então 20310 = 110010112
A conversão inversa (binário → decimal) é trivial.
Exemplo:
110010112 = 1 × 27 + 1 × 26 + 1 × 23 + 1 × 2 + 1 = 20310
Notação octal/hexadecimal
É comum utilizar-se a notação hexadecimal, (base 16) para exprimir
de forma mais compacta números binários. Cada dı́gito hexadecimal
equivale a 4 dı́gitos binários.
Noutra notação, denominada octal, o número é representado na base
8, em que cada dı́gito octal (0 . . . 7) agrupa 3 bits.
Em hexadecimal são utilizados os 16 sı́mbolos 0, 1, 2, . . . , 9, A, B, C,
D, E, F. Por exemplo, o número 20010 pode ser escrito como
110010002 , C816 ou 3108 .
Binário
0000
0001
0010
0011
0100
0101
0110
0111
Decimal
0
1
2
3
4
5
6
7
Hexa.
0
1
2
3
4
5
6
7
Octal
0
1
2
3
4
5
6
7
Binário
1000
1001
1010
1011
1100
1101
1110
1111
Decimal
8
9
10
11
12
13
14
15
Hexa.
8
9
A
B
C
D
E
F
Octal
10
11
12
13
14
15
16
17
A transcrição de um número inteiro na base 10 para base 2 pode ser
facilitada, sobretudo quando se trata de números grandes, fazendo
primeiro a transcrição para hexadecimal, e utilizando a tabela acima
para transcrever cada dı́gito hexadecimal na correspondente
sequência de 4 algarismos binários.
Adição de números inteiros
Como é que os computadores fazem operações aritméticas?
Comecemos por um exemplo simples, a adição de números inteiros.
Começando pelo bit menos significativo, adicionam-se os 2 primeiros
bits, escrevendo por baixo a respectiva soma. Se o resultado exceder a
capacidade de um único bit faz-se o “transporte” de um 1 para a
coluna seguinte, e soma-se esse transporte conjuntamente com os bits
dessa coluna.
Por conveniência, vamos representar no bit Ci de um conjunto auxiliar
de n bits o transporte que vai da coluna i para a coluna i + 1 e nos bits
Ti de um outro conjunto auxiliar o transporte que vem da coluna i − 1
para a coluna i. A operação adição em binário toma então a seguinte
forma simbólica:
Tn
Tn−1
...
T1
T0
An
An−1
. . . A1
A0
Bn
Bn−1
. . . A1
B0
Sn
Sn−1
...
S1
S0
Cn
Cn−1
. . . C1
C0
Nota: T0 = 0 e Ti = Ci − 1.
Se Cn 6= 0 → ocorreu transbordo (overflow) !
Adição de números inteiros (cont.)
Consideremos um computador que utiliza 8 bits para codificar
inteiros. O maior número inteiro representável nesta codificação é
255 (confirme!).
A operação de adição em binário efectua-se de forma análoga à
adição em decimal:
Exemplo: 100 + 88 = 188.
10010 = 011001002
8810 = 010110002
T
10000000
A
01100100
= 100
B
01011000
= 88
S
10111100
= 188
C
01000000 C7 = 0 → OK
Exemplo de transbordo em 8 bits: 189 + 88 = 277 > 28 − 1 = 255.
T
11110000
A
10111101
= 189
B
01011000
= 88
S
00010101
6= 277
C
11111000 C8 = 1: resultado inválido
Adição de números inteiros (cont.)
Os dı́gitos binários S e C são funções (booleanas) de 3 variáveis: os
dois digitos a adicionar e o transporte anterior:
S(A, B, T );
C(A, B, T ).
A tabela de verdade destas funções booleanas é a seguinte:
A
B
T
S
C
0
0
0
0
0
0
0
1
1
0
0
1
0
1
0
0
1
1
0
1
1
0
0
1
0
1
0
1
0
1
1
1
0
0
1
1
1
1
1
1
Pode-se mostrar que as funções S e C têm a seguinte forma algébrica,
S(A, B, T ) = ĀB̄T + ĀBT̄ + AB̄T̄ + ABT
C(A, B, T ) = AB + BT + AT
onde a disjunção e conjunção lógicas são representandas pelo soma e
produto, e X̄ representa a negação do valor lógico de X. (Verifique a
tabela de verdade!)
Implementação electrónica das operações lógicas
É possı́vel implementar funções lógicas utilizando circuitos
electrónicos elementares (gates) capazes de executar as operaç ões
lógicas “e” (AND), “não” (NOT) e “ou” (OR).
Se representarmos os valores “0” e “1” por um sinal eléctrico (ex. 0 =
0 volts e 1 = 5 volts), as gates dão um sinal eléctrico à saida
correspondente ao valor lógico da operação. Estes circuitos são
representados internacionalmente pelos seguintes sı́mbolos:
Α
Α
¬Α
Α
Α∧Β
Β
NOT
Α∨Β
Β
AND
OR
A
B
A∧B
A
B
A∨B
A
¬A
0
0
0
0
0
0
0
1
0
1
0
0
1
1
1
0
1
0
0
1
0
1
1
1
1
1
1
1
Gates lógicas
Circuito somador de 1 bit
A
¬A
B
¬B
T
¬T
AB
AT
C
BT
¬A¬BT
¬AB¬T
S
A¬B¬T
ABT
Somador de 1 bit.
Circuito somador de 16 bits
B15 A15 T15
Somador
1 bit # 15
C15 S15
B 2 A 2 T2
Somador
1 bit # 2
C2
S2
B 1 A 1 T1
Somador
1 bit # 1
C1
S1
B 0 A 0 T0 = 0
Somador
1 bit # 0
C0
S0
S = A+B
Somador de 16 bits.
A = A15 · · · A2 A1 A0
B = B15 · · · B2 B1 B0
S = S15 · · · S2 S1 S0
T0 = 0
Ti+1 = Ci
Testando o valor do bit C15 é possı́vel verificar se ocorreu transbordo.
A operação é inválida se C15 = 1.
NB: Actualmente, as somas são efectuadas por um outro tipo de
circuitos, diferente do apresentado – multiplexadores.
Codificação de números relativos
Existem vários códigos que permitem codificar números inteiros
relativos (códigos bipolares).
O código bipolar mais simples é o código de sinal e valor absoluto:
• o bit mais significativo representa o sinal
• os restantes n − 1 bits o valor absoluto.
Por convenção, o bit de sinal tem o valor 1 quando o número é
negativo e o valor 0 quando é positivo.
Por exemplo, numa palavra de 8 bits, os números ±19 têm, neste
código, a seguinte representação binária:
+1910 = 00010011
−1910 = 10010011
Vantagem deste código: simplicidade. Desvantagens:
• podem-se representar apenas 2n − 1 números distintos com n bits
e não 2n (o zero tem representação dupla)
• a adição de números relativos é problemática.
Código de complementos de 2
No código de complementos de 2 representam-se os números
positivos como no código anterior, e representa-se cada número
negativo somando 1 ao complemento do número positivo que lhe
corresponde. Um eventual transbordo que ocorra nesta soma é
ignorado. Vejamos como representar o número −19 numa palavra de
8 bits usando este código:
+1910 = 00010011
−1910 = 11101100 + 00000001 = 11101101
Regra prática para converter um número binário no respectivo
complemento de 2:
• copiam-se os dı́gitos, a começar pelo bit menos significativo até
encontrar o primeiro “1”, que também se copia. A partir daı́,
substituem-se os “0”s por “1”s e vice-versa.
Vantagens da representação de complementos de 2:
• O zero tem representação única (verifique!).
• O bit mais significativo pode ser interpretado como bit de sinal:
este bit é zero nos números positivos e 1 nos números negativos.
• O bit menos significativo determina sempre se um número é par
ou ı́mpar, o bit é zero nos números pares e 1 nos ı́mpares.
• O número total de números que é possı́vel representar neste
código numa palavra de n bits é 2n , correspondendo ao intervalo
−2(n−1) , . . . , 2(n−1) − 1.
Adição de números relativos
A implementação da adição de números relativos é muito simples em
complementos de 2. Utiliza-se o mesmo algoritmo da adição de
números inteiros, sendo apenas necessário ter atenção a um eventual
transbordo, cuja detecção é mais delicada. Exemplo:
T
00001010
A
00000101
=
5
B
11110101
=
−11
S
11111010
=
−6
C
00000101
A subtracção é feita de forma semelhante: para subtrair A de B,
soma-se a B o complemento de 2 de A , ou seja B − A = B + (−A).
Transbordo
A inspecção do bit de transporte Cn já não é suficiente para testar se
ocoreu transbordo! Exemplos:
T
11111110
A
00000001
=
1
B
11111111
=
−1
S
00000000
=
0
C
11111111
bit de transporte = 1; resultado correcto.
Adição de números relativos (cont.)
T
11111110
A
01111111
=
127
B
00000001
=
1
S
10000000
=
−128
bit de transporte = 0; resultado
C 01111111
incorrecto.
As seguintes regras devem ser aplicadas para verificar se há
transbordo numa soma de números inteiros relativos em
complementos de 2:
1. Se A e B têm sinais diferentes, nunca ocorre transbordo na
operação A + B e o resultado é sempre válido.
2. Se A e B têm o mesmo sinal e A + B o sinal oposto, então
ocorreu transbordo e o resultado é inválido.
Forma altenativa de detectar um resultado inválido: comparar os
bits Tn e Cn ; se não forem iguais ocorreu transbordo e o resultado
da operação é inválido.
Multiplicação e divisão de números inteiros relativos
Para multiplicar um número por 2, basta deslocar os bits para a
esquerda uma casa, colocando um 0 no bit menos significativo. Para
dividir um número por 2, deslocam-se todos os bits para a direita uma
casa, mantendo o bit do sinal com o mesmo valor.
Exemplo:
54 = 00110110
54 ∗ 2 = 54 << 2 = 01101100
54/2 = 54 >> 2 = 00011011
No caso de números ı́mpares, o deslocamento para a direita dá como
resultado a divisão por 2 arredondada para −∞, ou seja, 51 deslocado
de um bit para a direita dá como resultado 25; a mesma operação
efectuada em −51 dá como resultado −26. Verifique!
As operação de deslocamento de bits podem ser implementadas de
forma eficiente nos circuitos de electrónica digital.
As multiplicações (e divisões) de inteiros por potências de 2
contam-se entre as operações mais rápidas que o processador pode
efectuar, bem mais rápidas do que uma multiplicação ou divisão por
outros números!
Combinando as operações de adição e de shift é possı́vel efectuar
todas as operações aritméticas sobre inteiros.
Números reais: representação em vı́rgula flutuante
Os números reais formam um conjunto denso → não é possı́vel
representar exactamente um (intervalo) de reais num computator!
Representação em vı́rgula fixa: número fixo de casas decimais,
digamos 6. Basta codificar um inteiro (o número sem a vı́rgula)!
Não é uma boa escolha → limita a gama dinâmica de valores que se
podem representar com um conjunto de n bits.
Utiliza-se a notação em vı́rgula flutuante, semelhante à notação
cientı́fica:
mmmmmm × 10eeee ,
• mmmmmm → mantissa
• eeee → expoente
Por exemplo, o número 0.000341237 pode ser escrito de várias
formas:
0.00341237 × 10−1 , 0.0341237 × 10−2 , . . . 3.41237 × 10−4
A última é a forma canónica normalizada (não tem zeros atrás do
ponto decimal).
A representação em vı́rgula flutuante é efectuada habitualmente em
32, 64 ou 128 bits (precisão simples, dupla, e quádrupla).
Em precisão simples:
s eeeeeeee
mmmmmmmmmmmmmmmmmmmmmmm
Representação em vı́rgula flutuante (IEEE754)
• bit 31 → bit de sinal
• bits 30 · · · 23 expoente
• bits 22 · · · 0 mantissa
Nota: utiliza-se normalização, pelo que se subentende que o primeiro
algarismo da mantissa é diferente de zero. Na base 2 este algarismo
só pode ser 1, e portanto não se representa. Assim, usando 23 bits a
mantissa tem 24 bits efectivos de precisão!
O expoente é codificado com um offset de 127, ou seja é preciso
subtrair 127 ao número inteiro que representa o expoente para obter o
seu valor real.
Os valores reais do expoente com significado variam entre -127 e 127
(0 a 254 sem offset). Reserva-se o padrão eeeeeeee = 11111111 = 255
para representar os infinitos (s = 0, m = 0, +∞), (s = 1, m = 0, −∞).
Também existem dois “zeros” (s = 0, m = 0, 0+ ) e (s = 1, m = 0, 0− ).
Os padrões com a mantissa 6= 0 e expoente = 255 não representam
números, mas códigos de erro (NAN - not a number, por exemplo
divisão por zero).
P.
bits sinal
mants.
expo.
min
max
alg. sign.
S
32
1
23
8
1.2E-38
3.4E38
6–9
D
64
1
52
11
2.2E-308
1.8E308
15-17
Representação em vı́rgula flutuante (cont.)
Numa codificação de 32 bits, não se pode armazenar mais informação
do que a permitida pelos 32 bits→ o número de “números reais” com
representação distinta neste esquema de codificação = 232 , tal como
para os números inteiros. A interpretação dos bits é diferente, mas o
número de possibilidades distintas de codificação é o mesmo. No
caso dos números inteiros, a distribuição é uniforme, mas já o mesmo
não se passa com a representação em vı́rgula flutuante!
Os “números reais” representados neste formato não estão
uniformemente distribuidos.
De facto, a distribuição é uniforme numa escala logarı́tmica:
• existem tantos números (224 para uma mantissa de 23 bits, ou
seja cerca de 8 milhões) entre 2−3 e 2−2 como entre 252 e 253 .
A representação dos números reais em vı́rgula flutuante não é (em
geral) exacta e a aritmética efectuada com esta representação também
não! O processo de normalização pode causar sérios erros de
arredondamento.
Exemplo:
matematicamente 1 + x − 1 = x, mas em aritmética de vı́rgula
flutuante 1 + x − 1 pode resultar num número muito diferente de x.
Caracteres
Da mesma forma que sequências de bits podem representar números,
também podem representar letras e outros sinais de pontuação.
O código mais comum para representação de caracteres é o ASCII
(American Standard Code for Information Exchange).
A codificação é feita em 7 bits, sendo possı́vel representar 27 = 127
sı́mbolos. Alguns destes sı́mbolos correspondem a caracteres de
controle de periféricos (impressora, modem...).
Nota: no código ASCII há um offset constante = 32 entre o código
das letras minúsculas e maiúsculas. Bit # 6 = 1 → letra minúscula.
Outros códigos: EBCDIC (IBM), Icon (Unysis), em desuso.
No código ASCII estão contemplados todos os caracteres da lı́ngua
inglesa, mas não caracteres utilizados noutras lı́nguas, como os
caracteres acentuados e o “ç” do português.
É possı́vel utilizar o oitavo bit para extender o conjunto ASCII com
127 novos sı́mbolos que incluem todos os caracteres acentuadas da
maioria das lı́nguas ocidentais mas não de outras regiões do mundo.
Para fazer face a este problema foi desenvolvido um novo standard
internacional, unicode, que se está a impor rapidamente. Na versão
3.2 do unicode cada caracter é codificado num (máximo) de 32 bits,
dando para 4 294 967 296 caracteres/pictogramas diferentes.
Suficiente, mesmo para o chinês!
Codificação de som
O formato digital de som mais comum é o PCM CD-Digital Audio
(PCM CD-DA) que é utilizado para gravar música em CDs. Este
formato foi especificado em 1980 pela Philips e pela Sony, inventores
do disco compacto.
O som é digitalizado com uma velocidade de amostragem de 44.1
kHz, cerca do dobro da frequência máxima audı́vel para os humanos.
A amostragem é feita em 2 canais (estéreo), com uma resolução de 16
bits. Assim, cada segundo de som num CD ocupa
44100 × 2 × 2 = 176400 octetos.
Os dados são registados no disco em blocos de 2352 octetos. Cada
segundo de som ocupa 75 blocos. Um CD tem a capacidade de 74
minutos de registo, ou seja 2352 × 75 × 74 × 60 = 783216000
octetos, cerca de 747 MB.
Cada minuto de música no formato PCM CD-DA, sem compressão,
ocupa 10 MB!
Naturalmente, técnicas de compressão de dados permitem hoje em
dia registar som, com boa qualidade, ocupando menos “espaço”. É o
caso dos formatos digitais MP3 e OGG, populares na internet.
Existem ainda vários outros formatos “aúdio” em uso, como o
utilizado pela Microsoft nos ficheiros “WMA”.
Recentemente, a dupla Philips/Sony lançou um novo formato de
áudio digital, o Super Áudio CD: SA-CD. Este formato tem uma
qualidade áudio superior e permite registar vários canais (suround).
Codificação de imagem
Uma imagem pode ser guardada em formato digital (scanner, câmara
fotográfica digital,...) e processada por computador.
Na digitalização, a imagem é dividada num número elevado de
pequenos elementos - os pixels. Uma imagem registada numa câmara
fotográfica digital tem entre 2–5 milhões de pixels.
Imagem a preto e branco (sem graus de cinzento): cada pixel só
contém um elemento de informação (branco ou negro) que pode ser
representado por um unico bit. Uma imagem de 600 × 400 pixels
ocupa cerca de 24k octetos.
Sendo necessário registar nı́veis de cinzento, por exemplo de uma
fotografia a preto e branco, é comum utilizar-se um octeto (8 bits) por
pixel, o que permite descriminar entre 28 = 256 nı́veis de cinzento.
Imagem a cores: é necessário codificar, para cada pixel, a
percentagem das cores primárias, vermelho (R), verde (G) e azul (B)
do pixel. Se utilizarmos 8 bits para codificar a percentagem de cada
uma das cores primárias, serão necessários 24 bits por pixel. A esta
codificação dá-se o nome de RGB de 24 bits. Permite representar
224 ∼ 16.7 milhões de cor distintas.
Uma imagem colorida codificada em RGB-24, mesmo de pequena
dimensão, ocupa muitos octetos!
Daı́ ser comum utilizar uma técnica de compressão (JPG, GIF, ...) que
permita reduzir fortemente o “espaço” ocupado pela imagem
digitalizada, sem degradar apreciavelmente a sua qualidade.
Código executável (programas)
Os programas, a serem executados pelo processador, também são
armazenados em código binário e têm de ser transferidos para a
memória RAM do computador antes da sua execução.
O programa é uma sequência de instruções. O conjunto de instruções,
e a sua codificação, variam muito de processador para processador,
pelo que um certo código-máquina só corre num dado processador.
Suponhamos, um processador de 16 bits onde os 4 bits mais
significativos são usados para codificar a instrução e os 12 bits
restantes os dados, com o seguinte conjunto de instruções:
Código
Instrução
Operando
0000
Pausa/start
-
0001
Carregar registo
# memória
0010
Transferência p/ memória
# memória
0011
Soma (ao registo)
# memória
0100
Multiplica (o registo por)
# memória
0101
Salto
# memória
Consideremos o seguinte programa que ocupa os endereços 0. . . 21
(0. . . 15 em hexadecimal) da memória:
Endereço
Instruções/Dados
0,1
0000
2,3
1010
4,5
4012
6,7
300E
8,9
3014
A,B
2016
C,D
5000
E,F
0010
10,11
0011
12,13
0100
14,15
0111
Interpretando cada uma das instruções, verificamos que este programa
efectua o cálculo 17 × 32 + 16 + 49, ou ainda Y ∗V + X +W , onde X,
Y , V e W são os valores armazenados nos pares de octetos 14-15,
16-17, 18-19, 20-21, sendo o resultado armazenado nos octetos 22-23.
Utilizando uma linguagem mnemónica das instruções
código-máquina (assembler) o programa fica mais legı́vel:
X #14,16
Y #16,17
Z #18,32
W #20,49
U #22,0
LOD Y
MTP V
ADD
ADD
STR
JMP
X
W
U
STP
Numa linguagem de alto nı́vel, este pequeno programa seria expresso
pelo seguinte código:
X = 16
Y = 17
V = 32
W = 49
U = Y*V+X+W
print U
Um outro programa (compilador ou interpretador) traduz estas
instruções em código-máquina, previamente à sua execução. Num
compilador, a tradução do programa é efectuada de uma só vez. Num
interpretador as instruções são traduzidas uma de cada vez, à medida
que são executadas.
A escrita de um programa numa linguagem de alto nı́vel é muito
menos penosa do que em linguagem máquina, ou assembler.
Existem várias linguagens de alto nı́vel: FORTRAN, Pascal, Ada, C,
Módula, BASIC, LISP, Scheme, Algol, Erlang, Java, Python, etc.
Linguagens de programação
• 1949: Primeiro Assembler (ShortCode).
• 1951: Grace Hooper escreveu o primeiro compilador.
• 1957: FORTRAN: FORmula TRANslator. A linguagem
dominante no cálculo cientı́fico. Várias encarnações: FORTRAN
IV, FORTRAN 77, FORTRAN 90, FORTRAN 95, FORTRAN
2000.
• 1958: LISP (John McCarthy, MIT): Desenhada para Inteligência
Artificial. Novo paradigma de programação.
• 1958: ALGOL: criada por um comité cientı́fico internacional
para uso cientı́fico. Foi a primeira linguagem a dispôr de uma
gramática formal BNF. Recursão. ALGOL68.
• 1959: COBOL: COmmon Business Oriented Language
(contabilidade).
• 1964: BASIC (John Kemeny, Thomas Kurtz). Linguagem
interpretada. Simples, embora limitada.
• 1968: PASCAL (Niklaus Wirth, ETH). Programação estruturada.
Desenvolvida num ambiente académico, encoraja bons hábitos
de programação. Alocação dinâmica de memória. Sucessores:
MODULA, MODULA-2.
• 1972: C (Dennis Ritchie, Bell Labs): Sucedeu ao ”B”e ao
”BCPL”, inclui muitas das estrutura de controlo de fluxo tornadas
populares pelo Pascal. Desenhada para ser simples, rápida e
eficiente (aritmética de pointers, alocação dinâmica de memória).
Próxima da máquina. Poderosa mas não amigável. Utilizada para
programação do sistema. Ligação estreita com o UNIX (escrito
em C). Classificada como “assembler de alto nı́vel”...
• 1983: C++ (Bjarne Stroustroup ): C with Classes. Programação
orientada por objectos.
• 1987: Perl (Larry Wall): Linguagem de scripting. Poderosa para
tratamento de texto, internet.
• 1990: Python (G. van Rossum). linguagem de scripting para o
sistema distribuido Amoeba, nasceu num meio académico (Univ.
de Amesterdão). Descendente do ABC. Programação orientada
por objectos, influenciada pelo C e muitas outras linguagens.
• 1994: Java (Sun Labs). Multiplataforma, compilação intermédia
para bytecode, programação na internet.
Paradigmas de programação...
• Programação imperativa: FORTRAN, PASCAL, C, ..., Python.
• Programação funcional: LISP, Scheme, Miranda, Erlang, ...,
possı́vel em Python.
• Programação por objectos: Smalltalk ,C++, Java, Python.
• Scripting: Perl, TCL/Tk, Python.
Sistema operativo
Sistema operativo: programa de baixo-nı́vel que tem a seu cargo a
interface com o hardware, incluindo periféricos. Distribui os recursos
do sistema (tempo de CPU, memória) entre as várias tarefas em
execução (programas) .
Sistema operativo moderno:
• núcleo (kernel): pequeno programa que está sempre a correr no
computador e efectua a gestão dos recursos (atribuição de
memória, prioridades dos programas);
• programas que intercomunicam com o kernel e entre si, muitas
vezes numa relação de cliente/servidor.
Alguns sistemas operativos incluem de raı́z uma interface gráfica
(Windows), outros não. Não há só o Windows...
Sistemas operativos: 386BSD, AIX, AOS, Amoeba, Angel, Artemis
microkernel, BeOS, Brazil, COS, CP/M, CTSS, Chorus, DACNOS,
DOSEXEC 2, GCOS, GEORGE 3, GEOS, ITS, KAOS, Linux,
LynxOS, MPV, MS-DOS, MVS, Mach, MacOS, MINIX, Multics,
Multipop-68, Novell NetWare, OS-9, OS/2, Pick, Plan 9, QNX, RISC
OS, STING, System V, System/360, TOPS-10, TOPS-20, TRUSIX,
TWENEX, TYMCOM-X, Thoth, Unix, VM/CMS, VMS, VRTX,
VSTa, VxWorks, WAITS, Windows 3.1, Windows 95, Windows 98,
Windows NT, . . . .
Sistemas operativos mais populares:
• grandes sistemas: UNIX, VMS, AX/P, AU/X.
• computadores pessoais: Windows, MacOS, Linux, FreeBSD.
O que é o Python?
O Python é uma VHLL (Very High Level Language).
O próprio criador da linguagem dá dela a seguinte definição:
Python is an interpreted, interactive, object-oriented programming
language. Python combines remarkable power with very clear syntax.
It has modules, classes, exceptions, very high level dynamic data
types, and dynamic typing.
Guido van Rossum (Univ. Amesterdão), que criou esta linguagem de
programação em 1991, teve como fonte de inspiração para o nome a
série britânica de humor da década de 70 “Monty Python Flying
Circus”, de que é fã incondicional. . .
And now for something different...
Who ordered the Spanish Inquisition?
O Python ainda está em desenvolvimento por uma grande equipa de
colaboradores, liderada por GvR. A licensa de utilização é do tipo
GPL. Está disponı́vel gratuitamente (em código fonte, C) para a
maioria dos sistemas operativos.
Humor pitónico...
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren’t special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one– and preferably only one –obvious way to do it.
Although that way may not be obvious at first unless you’re Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it’s a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea – let’s do more of those!
copyleft: Tim Peters.
Programação em Python
Estrutura de um programa em Python:
• Os programas são compostos de módulos
• Os módulos contêm instruções e expressões
• As instruções e as expressões criam e processam objectos.
O que é um objecto?
É dificil dar, nesta altura, uma definição rigorosa deste conceito. De
uma forma simples, os objectos correspondem a uma certa região da
memória do computador à qual está associada um endereço único e
na qual estão armazenados:
• dados
• informações sobre os dados
• funções que actuam sobre estes dados.
A instrução básica em Python consiste em criar um objecto e dar-lhe
um nome; a esta instrução dá-se o nome de atribuição (de nome!).
Exemplo:
>>> x = 123
cria o objecto 123, algures na memória do computador e dá-lhe o
nome x. Diz-se também que x é uma referência para o objecto 123
ou que “aponta” para o objecto 123, tudo com o mesmo significado.
Depois de criados, os objectos passam a ser referidos através dos seus
nomes. Por exemplo, a instrução:
>>> print x
imprime no ecrã o valor do objecto cujo nome é x. Nem todos os
objectos têm um valor, mas todos os objectos têm um tipo e um
endereço únicos.
Para obter o tipo de um objecto, utiliza-se a instrução type(nome),
neste caso type(x).
>>> x = 123
>>> type(x)
<type ’int’>
>>>
Para obtermos o endereço de um objecto utiliza-se a instrução
id(nome). Assim:
>>> x = 1.23456
>>> print x
1.23456
>>> type(x)
<type ’float’>
>>> id(x)
135625436
>>>
É possı́vel dar mais do que um nome a um mesmo objecto. A esta
operação dá-se o nome de aliasing e pode revelar-se útil em certas
casos.
>>> x = 45
>>> y = 45
>>> id(x)
135363888
>>> id(y)
135363888
>>>
Pode-se ainda fazer-se este “baptismo duplo” na mesma linha:
>>> x = y = 45
>>> id(x)
135363888
>>> id(y)
135363888
>>>
No entanto, um mesmo nome não pode ser usado por mais do que um
objecto ao mesmo tempo!
>>> x = 20
>>> x = 43
>>> print x
43
>>>
Prevalece sempre a última instrução de atribuição... O objecto 20
deixou de ter nome e o Python encarregar-se-á de apagar os objectos
sem nome atribuı́do da memória do computador (recolha de lixo).
Tipos e categorias de objectos
Cada objecto tem um tipo e uma categoria.
Existem vários tipos de objectos:
• os números, dos quais há os seguintes tipos: inteiros, inteiros
longos, números em vı́rgula flutuante, números complexos.
• as colecções: sequências e mapas. Nas sequências temos os tipos
listas, tuplas e cadeias de caracteres. Nos mapas, existe um tipo
único, o dicionário.
• funções, classes e métodos
• ficheiros
• etc...
Cada objecto pertence a uma das categorias: mutável ou imutável.
Um objecto imutável não pode ser alterado, uma vez criado só lhe
podemos dar ou mudar o nome, ou destruı́-lo. Um objecto
mutável pode sobre alterações.
Nota: um objecto pode destruir-se com a instrução del.
>>> x = 123
>>> print x
123
>>> del(x)
>>> print x
Traceback (most recent call last):
File "<stdin>", line 1, in ?
NameError: name ’x’ is not defined
Importante: os números, as cadeias de caracteres e as tuplas são
objectos imutáveis.
Algumas notas sobre os tipos numéricos:
• inteiros: representação em complementos de 2 usando 32 bits.
• inteiros longos: quando o número inteiro está fora do intervalo
[−2147483648, 2147483647], utiliza o número de bits
necessário, dentro da disponibilidade de memória do computador.
• vı́rgula flutuante: representação IEEE754 de dupla precisão (64
bits.)
• complexos: a parte real e imaginária são codificados em 64 bits.
os sı́mbolo j e J representam a unidade imaginária. Exemplo: z =
2+3j.
As expressões aritméticas habituais podem ser usadas com objectos
do tipo numérico. A conversão de inteiro → longo inteiro → vı́rgula
flutuante funciona do modo esperado, excepto no caso da divisão de
inteiros, que é entendida como a divisão inteira.
Exemplos:
>>> n = 1
>>> z = 2*n
>>> print z
2
>>> x = 1.23456
>>> print 2*3.456+5*x
13.0848
>>> print z/3
0
Expressões aritméticas
Estão disponı́veis os seguintes operadores aritméticos binários:
Operador
Significado
Precedência
+
soma
0
-
subtracção
0
*
multiplicação
1
/
divisão
1
//
divisão inteira
1
%
resto da divisão inteira
1
**
potência
2
Na avaliação de uma expressão aritmética a operador potência têm o
mais elevado grau de precedência, seguindo-se a multiplicação,
divisão e resto da divisão (todas ao mesmo nı́vel) e por último as
somas e subtracções. Exemplo:
>>> print 2**3+2*2
12
Quando se pretenda impor uma ordem de avaliação diferente da
imposta pelos graus de precedência dos operadores, devem utilizar-se
parêntesis:
>>> print 2**(3+2*2)
128
Operadores bit a bit
É também possı́vel efectuar operações bit a bit com os seguintes
operadores:
Operador
Significado
Precedência
|
OR binário
0
‘
XOR binário
1
&
AND binário
2
>>
deslocamento para a direita
3
<<
deslocamento para a esquerda
3
∼
NOT binário
4
>>> x = 1
>>> x << 2
4
Notar que as operações aritméticas têm precedência sobre as
operações bit a bit!
>>> x = 1
>>> x <<2+1
8
>>> (x<<2)+1
5
Números complexos
Números imaginários são escritos com o sufixo j or J. Números
complexos têm a forma (real+imag j), e podem também ser
criados com a função complex(real,imag).
>>> 1j * 1J
(-1+0j)
>>> 1j * complex(0,1)
(-1+0j)
>>> 3+1j*3
(3+3j)
>>> (3+1j)*3
(9+3j)
>>> (1+2j)/(1+1j)
(1.5+0.5j)
Um número complexo z possui uma parte real e imaginária,
accessı́veis atravéz de z.real e z.imag.
Para calcular o módulo do número complexo usa-se a função
abs(z).
>>>
>>>
3.0
>>>
4.0
>>>
5.0
>>>
z = 3.0+4.0j
z.real
z.imag
abs(z)
# sqrt(z.real**2 + z.imag**2)
Não é possı́vel converter directamente um número complexo num
número inteiro ou de vı́rgula flutuante, mesmo quando a sua parte
imaginária é zero!
>>> a = complex(3,0)
>>> float(a)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: can’t convert complex to float;
use e.g. abs(z)
As funções disponı́veis no módulo math não funcionam com
números complexos. As funções para números complexos estão
disponı́veis no módulo cmath.
>>> import math,cmath
>>> print math.sqrt(-1)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ValueError: math domain error
>>> print cmath.sqrt(-1)
1j
>>> z = cmath.exp(cmath.pi*1j)
>>> print z.real,z.imag,abs(z)
-1.0 1.22460635382e-16 1.0
Cadeias de caracteres (strings)
As sequências de caracteres, tal como os números, são objectos
imutáveis. Um objecto do tipo sequência de caracteres é criado
delimitando a sequência por pares de aspas (simples, duplas ou
triplas) e atribuindo-lhe um nome. Exemplos:
s1
s2
s3
s4
=
=
=
=
’This is a string’
"This is also a string, for sure."
"Is this a string? Yes, it’s a string."
"""
This is a string
with two lines.
"""
As cadeias de caracteres são sequências ou seja, são objectos
indexáveis. O ı́ndice de cada caracter é um número que indica a sua
posição na sequência. O primeiro caracter tem o ı́ndice 0, o segundo o
ı́ndice 1, etc. Para uma cadeia com n caracteres, o último caracter tem
o ı́ndice n-1. É um erro tentar aceder a um ı́ndice inexistente.
>>> s =’spam’
>>> print s[0],s[2]
s a
>>> print s[4]
Traceback (most recent call last):
File "<stdin>", line 1, in ?
IndexError: string index out of range
O comprimento de uma sequência pode ser obtido através da função
len.
>>> town = ’Coimbra’
>>> print len(town)
7
Um ı́ndice negativo i de uma sequência s é interpretado como i +
len(s). Assim, s[-1] é o mesmo que s[len(s)-1], ou seja
representa o último elemento da sequência e s[-2] o penúltimo.
>>> town = ’Coimbra’
>>> print town[-1],town[-2]
a r
>>> print town[-8]
Traceback (most recent call last):
File "<stdin>", line 1, in ?
IndexError: string index out of range
Duas ou mais sequências podem ser concatenadas com o operador +.
>>> phrase = "This " + "is " + "spam"
>>> phrase
’This is spam’
A operação n*s em que n é um inteiro, é interpretada no sentido de n
adições, funcionando como operador de repetição:
>>> print 3*’abc’
abcabcabc
Não se pode alterar uma string, uma vez que é um objecto imutável.
>>> name = ’Pythom’
>>> name[5] = ’n’
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: object doesn’t support item assignment
As sequências de caracteres possuem um conjunto rico de métodos,
que são invocados com a sintaxe s.method(). Como as strings são
objectos imutáveis, os métodos de sequência devolvem novas
sequências, nunca alterando a sequência original!
>>> s =’coimbra’
>>> print s.upper()
COIMBRA
>>> print s
>>> coimbra
>>> s = s.upper()
>>> print s
COIMBRA
Para conhecer os métodos que possui uma string, basta no modo
interactivo criar uma (qualquer) string s e digitar a instrução
dir(s). Há mais de uma dúzia de métodos úteis. Dois exemplos:
>>> s = ’coimbra is a nice town’
>>> s.title()
’Coimbra Is A Nice Town’
>>> s.replace(’town’,’place’)
’coimbra is a nice place’
Fatiagem de sequências
Entende-se por “fatia” de uma sequência, uma subsequência da
sequência. Todas as sequências em Python podem ser “fatiadas” com
uma sintaxe genérica, bastante poderosa.
Uma fatia da sequência S representa-se de forma genérica com a
notação S[i:j:k], cujo significado é o seguinte:
“Subsequência de S que se inicia com o elemento de ı́ndice i e
contém os elementos cujos ı́ndices são inferiores a j, saltando de k
em k elementos”. Assim, os elementos da fatia são os elementos de L
de ı́ndices:
i, i+k,i+2*k, ..., i<j
Se em S[i:j:k] o número i faltar é substituı́do por 0; se o número
j faltar, será substituı́do pelo maior número inteiro do computador.
Se k faltar será substituido pelo número 1. Exemplos:
>>> town = "Coimbra is a nice town"
>>> town[2:8]
’imbra ’
>>> town[2:10:2]
’ibai’
>>> town[5:]
’ra is a nice town’
>>> town[:10]
’Coimbra is’
>>> town[:-3:2]
’Cibai iet’
Listas
As listas são sequências heterogéneas e mutáveis de objectos. Para
criar um objecto deste tipo enumeram-se os elementos da lista,
separados por vı́rgulas, entre parêntesis rectos.
L = [’abc’, 123, ’Python’]
Uma lista vazia é representada por [].
Como as listas são objectos mutáveis, podemos alterar um objecto por
outro, ou remover um elemento da lista.
>>> stuff = [’123’,123,1 +3j,’numbers’]
>>> stuff
[’123’, 123, (1+3j), ’numbers’]
>>> del stuff[2]
>>> stuff
[’123’, 123, ’numbers’]
>>> stuff[0] = 2*123
>>> stuff
[246, 123, ’numbers’]
Para acrescentar um objecto a uma lista, utiliza-se o método append:
>>> clubs = [’Benfica’, ’Sporting’]
>>> clubs.append(’Porto’)
>>> clubs
[’Benfica’, ’Sporting’, ’Porto’]
Uma lista pode conter outras listas. Uma matriz 2 × 2 pode ser
guardada numa lista de elementos em que cada elemento é uma linha
da matriz, também representada numa lista:
>>> M = [[1,2,3],[4,5,6],[7,8,9]]
>>> print M[2][1]
7
Tal como as strings, as listas podem ser concatenadas e repetidas
usando os operadores + e *:
>>> L =[’a’,’ab’]
>>> L = L + L
>>> L
[’a’, ’ab’, ’a’, ’ab’]
>>> 2*L
[’a’, ’ab’, ’a’, ’ab’, ’a’, ’ab’, ’a’, ’ab’]
As listas podem conter referências a outros objectos.
>>> x = 123
>>> L = [x,x*x]
>>> L
[123, 15129]
>>> L = 2*L
>>> L
[123, 15129, 123, 15129]
>>> L = L[2:] + [2*L[0]]
>>> L
[123, 15129, 246]
>>> L.sort()
>>> L
[123, 246, 15129]
Tuplas
A grande diferença entre as tuplas e as listas é que as primeiras são
objectos imutáveis. Na construção de uma tupla utilizam-se
parêntesis curvos em vez de rectos:
>>> t = (1,’a’,1j)
>>> type(t)
<type ’tuple’>
>>> print t
(1, ’a’, 1j)
>>> type(t[2])
<type ’complex’>
Existe uma notação especial para designar uma tupla de 1 único
elemento: (’single’,) e não (’single’)!
Tal como as outras sequências, as tuplas podem ser indexadas e
concatenadas. O desempacotamento de uma tupla pode ser feito de
uma forma muito simples:
>>> x,y,z = (’one’,’two’,’three’)
>>> print x,y,z
one two three
O número de nomes do lado esquerdo do sinal = têm de ser igual ao
dos elementos da sequência, caso contrário ocorre um erro. O
desempacotamento também funciona para strings e listas.
>>> c1,c2,c3,c4 = ’Spam’
>>> print c3
a
As tuplas podem conter sequências que contêm outras sequências.
Apesar de serem objectos imutáveis, as tuplas, podem conter objectos
mutáveis, tais como listas:
>>> t = (’Python’,[’C’,’Pascal’,’Perl’])
>>> id(t)
1078912972
>>> lang = t[1]
>>> lang[2] = ’Python’
>>> print t
(’Python’, [’C’, ’Pascal’, ’Python’])
>>> id(t)
1078912972
Neste exemplo,a tupla t é imutável, mas porque um dos seus
elementos é uma lista, e esta foi alterada pelo programa, o resultado é
um pouco surpreendente... O adjectivo imutável refere-se à tupla em
si, não aos objectos contidos na tupla!
Embora as tuplas possam parecer redundantes face às listas, é por
vezes muito útil ter a certeza que uma certa “lista” de objectos não é
alterada. O empacotamento numa tupla é ideal para estes casos.
Veremos adiante que as tuplas têm um papel muito importante na
linguagem Python. Por exemplo, as funções podem devolver objectos
múltiplos empacotados numa tupla.
Dicionários
Os dicionários, que pertencem à categoria dos mapas, são a estrutura
de dados mais poderosa do Python. Podem ser indexadas por
qualquer objecto imutável, e não apenas por números (como acontece
com as sequências).
Tal como as listas, os dicionários são objectos mutáveis aos quais se
podem mudar, acrescentar ou eliminar elementos. Ao contrário das
listas, acrescentar um novo valor a um dicionário faz-se por simples
indexação do novo elemento.
>>> tel = {’pedro’: 4098, ’ana’: 4139}
>>> tel[’guida’] = 4127
>>> tel
{’pedro’: 4139, ’ana’: 4127, ’guida’: 4098}
>>> tel[’guida’]
4098
>>> del tel[’pedro’]
>>> tel[’berto’] = 5991
>>> tel
{’berto’: 5991, ’ana’: 4127, ’guida’: 4098}
>>> tel.keys()
[’berto’, ’ana’, ’guida’]
>>> tel.has_key(’ana’)
True
>>> ’ana’ in tel
True
Os objectos não são guardados numa forma ordenada, tal como
mostra o exemplo. Para testar se uma chave existe num dicionário
usa-se o método has key (ou o teste ’key’ in ’dict’).
É possı́vel obter uma lista das chaves e dos valores de um dicionário
recorrendo aos métodos keys() e values().
Para ordenar qualquer destas listas podemos utilizar o método sort.
>>> tel = {’berto’: 5991, ’ana’: 4127, \
’guida’: 4098}
>>> tel.keys()
[’guida’, ’berto’, ’ana’]
>>> tel.values()
[4098, 5991, 4127]
>>> keys = tel.keys()
>>> keys.sort()
>>> keys
[’ana’, ’berto’, ’guida’]
Tentar aceder a um elemento de um dicionário que não existe dá um
erro. No entanto, podemos substituir a indexação pela chamado do
método get(x,dft) que permite obter o valor por defeito dft
quando se acede a um elemento x inexistente (em vez de um erro).
>>> print d.get(’ana’,0),d.get(’fernando’,0)
4127 0
Esta função pode ser útil, por exemplo, na implementação de uma
matriz esparsa (em que a maioria dos elementos é zero) utilizando um
dicionário.
Identificadores e palavras reservadas
A atribuição de um nome a um objecto é uma das instruções mais
básicas de um programa em Python. Os nomes não podem começar
por números nem conter alguns caracteres com significado operatório
(como *,+,−,%), mas podem ter um comprimento arbitrário. Notar
que este-nome é ilegal mas este nome não. Também não podem
coincidir com uma das palavras reservadas da linguagem:
and assert break close continue def del elif
else except exec finally for from global if
import in is lambda not or pass print raise
return try else
Outras palavras que convém não utilizar como indentificadores são as
que correspondem a constantes pré-definidas como True, False
e None ou funções intrı́nsecas como float.
Como regra, nomes curtos mas sugestivos são a melhor escolha para
um identificador. Exemplo:
x = 0.034
é uma má escolha de um identificador, mas
inflation = 0.034 # inflation rate in portugal
é uma boa escolha (notar a curta explicação, quando o nome é
definido, a seguir ao sı́mbolo de comentário #), melhor do que a
alternativa desnecessariamente verbosa, embora sintacticamente legal:
portuguese_inflation_rate = 0.034
Estruturas de controle
O fluxo de um progama (sequência e encadeamento das instruções) é
ditado pelas estruturas de controle, que em Python são as seguintes:
• if ...
elif ...else
• while...else
• for...else
• try...except...finally
• raise
• break
• continue
Comparado com outras linguagens de programação, o número de
estruturas de controle é reduzido, o que facilita a aprendizagem. As
estruturas de controle foram desenhadas para serem poderosas e de
uso genérico. Por exemplo, a instrução for ... else permite
percorrer qualquer objecto iterável e não se limita a arrays (listas
homogéneas de objectos ocupando uma porção contı́gua da
memória), como em C ou Pascal e na maioria das outras linguagens
de programação.
Selecção: if...elif...else
if <test>:
<statements>
[elif <test>:
<statements>]
[else:
<statements>]
Exemplo:
if choice == ’eggs’:
print ’Eggs for lunch.’
elif choice == ’ham’
print ’Ham for lunch’
elif choice == ’spam’
print ’Hum, spam for lunch’
else:
print "Sorry, unavailable choice."
Nota importante:
O teste de igualdade entre x e y é x == y e não x = y!
Verdadeiro e falso
A interpretação do valor lógico “verdadeiro” (True) ou “falso”
(False) de um objecto em Python segue as seguintes regras:
• Um objecto é considerado verdadeiro se for diferente de zero,
caso seja um número, ou se o objecto não estiver ”vazio”.
• Um objecto é considerado falso se não for verdadeiro, ou seja nos
seguintes casos: número zero, objecto vazio ou o objecto None.
O valor lógico de um objecto é calculado pela função bool; o valor
devolvido é True ou False, os dois únicos objectos do tipo
bool(eano).
Regras para expressões:
• As comparações (== != > < >= <=) devolvem o valor
True ou False consoante sejam falsas ou verdadeiras.
• not a é False se a for verdadeiro e True se a for falso.
• x or y devolve o primeiro dos objectos que for verdadeiro; se
nenhum for verdadeiro, devolve o último objecto.
• x and y devolve o primeiro dos objectos que for falso; se
nenhum for falso, devolve o último objecto.
Os valores obtidos de acordo com estas regras são os esperados da
lógica de Boole.
Ciclos de repetição: while...else
while <test>:
<statements>
[if <test>: break]
[if <test>: continue]
<statements>
[else:
<statements>]
Nota: As cláusulas break, continue e else são opcionais. Se o
teste da cláusula break for verdadeiro, o programa salta para o fim
do bloco while. Se o teste da cláusula continue for verdadeiro, o
programa abandona o ciclo actual e continua no próximo ciclo. Se
existir a cláusula else e se a instrução break não for accionada, o
programa executa as instruções que se seguem a else no final dos
ciclos.
Exemplo 1:
a = 0; b = 10
while a < b:
print a,
a += 1 # a = a+1
Resultado: >>> 0 1 2 3 4 5 6 7 8 9
Exemplo 2:
x = 10
while x:
x -= 1
# x = x-1
if x % 2 = 0:
Resultado: >>> 8 6 4 2 0
Exemplo 3:
y = input(’Give an integer number: ’)
x = y/2
while x > 1:
if y % x == 0:
print y,’is not prime.’
break
x -= 1
else:
print y,"is prime!"
Resultado:
>>> Give an integer number:
>>> 5 is prime!
Exemplo 4:
name = ’Spam’
while name:
print name,
name = name[1:]
Resultado: >>> Spam pam am m
5
Exemplo 5:
# Guess a number game
mynumber = ’123456’
while 1:
n = input(’Guess the number: ’)
if n == mynumber:
print ’You guessed the number!’
break;
else:
print ’Sorry, wrong guess.’
print ’Game is over’
Resultado:
Guess the number: 43465
Sorry, wrong guess.
Guess the number: 7161527
Sorry, wrong guess.
Guess the number: 999999
Sorry, wrong guess.
Guess the number: 123456
You guessed the number!
Game is over.
Ciclos de iteração: instrução for ...else
Em Python a instrução for é utilizada (exclusivamente) para iterar
sobre objectos. Na versão actual do Python todas as sequências, os
dicionários e os ficheiros têm um iterador definido por defeito, que
devolve cada um dos seus elementos em sequência, mas é possı́vel
definir iteradores para outros objectos.
Sintaxe:
for x in object:
<instruções>
[if condição: break>]
[else:
<instruções>]
Se existir a cláusula opcional else, a instruções que se lhe seguem
serão executadas apenas no caso de o ciclo atingir o último elemento
do objecto sem a condição break ser accionada.
Exemplos:
basket = [’orange’,’banana’,’apple’]
for fruit in basket:
print fruit
phrase = ’Coimbra is a nice town.’
for c in phrase:
print c,ord(c) # ord(c) = ASCII code
Ciclos for: continuação
Exemplo de utilização da cláusula else. Suponhamos que queremos
testar se existe algum número negativo na lista L. Podemos fazê-lo
com o seguinte código:
for x in L:
if x < 0:
print ‘‘There are negative numbers’’
break
else:
print ‘‘All numbers are non-negative’’
O Python possui a função intrı́nseca range que é muito útil para
criar listas de inteiros que são frequentemente usadas para iterar ciclos
for:
range(n) -> [0,1,2, ...n-1]
range(i,j) -> [i,i+1,...j-1]
range(i,j,k) -> [i,i+k, i+2k, ...]
Exemplos:
range(5) = [0,1,2,3,4]
range(2,5) = [2,3,4]
range(1,10,2) = [1,3,5,7,9]
range(0,-10,-3) = [0,-3,-6,-9]
Para ciclos que exijam listas muito extensas é preferı́vel utilizar a
função xrange, semelhante à anterior, mas que permite economizar
memória, sendo a lista criada à medida que for necessário.
Para iterar no modo clássico, equivalente ao do Pascal ou C,
utilizando um ı́ndice inteiro que percorre a sequência, utiliza-se a
construção
>> for i in range(len(lista)):
>>
print lista[i]
que é totalmente equivalente à forma mais pitónica:
>> for x in lista:
>>
print x
Advertência importante sobre a utilização de ciclos for: a lista
iteradora não deve ser alterada no interior do ciclo sob pena de se
obterem resultados errados!
Exemplo (errado!):
for x in lista:
if x < 0: lista.remove(x)
Para o efeito devemos iterar não sobre a lista mas sobre uma cópia
(ou clone) da lista:
for x in lista[:]:
if x < 0: lista.remove(x)
A operação lista[:] cria um cópia da lista sobre a qual se efectua
a iteração, podendo agora a lista original ser manipulada, sem
problemas, no interior do ciclo.
Por vezes é útil, ao iterar sobre uma sequência, aceder ao elemento e
simultaneamente ao seu ı́ndice. Nestes casos, em vez de utilizar um
código do estilo:
for i in range(len(L)):
print i,L[i]
é mais prático e eficiente utilizar a função intrı́nseca enumerate:
for i,x in enumerate(L):
print i,x
A função enumerate está definida não apenas para sequências mas
para todos os objectos iteráveis: dicionários, ficheiros, etc. Por
exemplo, este programa imprime todas as linhas de um ficheiro
anotadas com o número da linha:
f = open("myfile.txt",’r’)
for i,line in enumerate(f):
print i,">",line
Para iterar simultâneamente sobre várias sequências existe uma outra
função muito útil: zip. Exemplo:
colors = ("red","green","blue")
clubs = ("Benfica","Sporting","Porto")
for club,color in zip(clubs,colors):
print club,color
Funções
As funções em Python são de certa forma semelhantes às funções da
linguagem C e às funções/procedimentos do Pascal.
Sintaxe:
def nome([arg1,arg2,...,argn]):
<statements>
return [value1,value2,...valuen]
Notas:
• a instrução def cria um objecto do tipo função e atribui-lhe um
nome
• return devolve o(s) resultado(s) para a instrução que chamou a
função
• para chamar uma função, depois de criado este objecto, basta
invocá-la pelo seu nome.
A passagem de argumentos é feita por referência, ou seja por
atribuição de um nome no espaço de nomes local da função. Isto
significa que objectos imutáveis não podem ser alterados dentro de
uma função (nem fora dela), mas os objectos mutáveis (ex. listas,
dicionários) sim.
Utilidade das funções:
• Reutilização do código
• Decomposição de uma tarefa complexa numa série de tarefas
mais simples (funções)
• Facilita a leitura e futuras modificações do programa
Exemplo 1:
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
def produto(x,y):
return x*y
print produto(2,3)
6
z =2
y = produto(9,z)
print y
18
frase = ’aa’
z = produto(frase,2)
print z
’aaaa’
Exemplo 2:
>>> def intersect(s1,s2):
>>>
res = []
>>>
for x in seq1:
>>>
if x in seq2:
>>>
res.append(x)
>>>
return res
>>>
>>> intersect([1,2,3],[1,7,2])
>>> [1,2]
Valores por defeito de argumentos
É por vezes útil atribuir valores por defeitos a um ou mais argumentos
de uma função, o que permite omiti-los na chamada da função.
Exemplo:
>>> def add_tax(x,iva=19):
...
"""Adds the IVA tax (given as percent)
...
to value x.
...
"""
...
return x*(1+iva/100.)
...
>>> print add_tax(100)
119.0
>>> print add_tax(100,5)
105.0
>>> print add_tax(100,iva=7)
107.0
Quando omitido, o argumento iva toma o valor por defeito (19%). O
exemplo ilustra ainda a possibilidade de chamar explicitamente por
nome um argumento da função, o que ajuda, por vezes, à legibilidade
do programa. Chama-se a atenção para o seguinte: na chamada de
uma função, a seguir a um argumento dado explicitamente por nome,
todos os outros argumentos que lhe seguem também terão que ser
dados da mesma forma, pelo que a seguinte chamada da função é
ilegal:
>>> print add_tax(buy=200,5)
SyntaxError: non-keyword arg after keyword arg
Notar a string de documentação a seguir à instrução def.
Valores por defeito de argumentos
É ainda notar que a resolução dos nomes dados por defeito na
definição de uma função sé é avaliada uma vez, precisamente quando
a função é definida, e não quando a função é chamada; este detalhe
pode dar origem a erros subtis!
>>> taxa = 19
>>> def add_tax(x,iva=taxa):
...
return x*(1+iva/100.)
...
>>> print add_tax(100,taxa)
119.0
>>> taxa = 20
>>> print add_tax(100)
119.0
Um outro resultado inesperado:
>>> def save(x,L=[]):
...
L.append(x)
...
print L
>>> save(1)
[1]
>>> save(2)
[1, 2]
>>> L =[’a’,’b’]
>>> save(3,L)
[’a’, ’b’, 3]
>>> save(3)
[1, 2, 3]
Resolução de nomes: regra de alcance
A resolução dos nomes no interior de uma função segue a seguinte
regra: em primeiro lugar é procurado o nome no espaço dos nomes
local da função, isto é os nomes que foram atribuı́dos a objectos no
interior da função através de uma instrução de atribuição, ou usando
as instruções def ou class. Se o nome não foi encontrado,
pesquisa-se de seguida o espaço dos nomes global, isto é, os nomes
definidos no módulo principal. Caso o nome também não exista neste
espaço, passa-se a pesquisar no espaço dos nomes pré-definidos
(built-in). Se o nome não for encontrado a este nı́vel é levantada uma
excepção “NameError: name is not defined.”
Este regra simples pode ser relembrada com a mnemónica LGB
(Local, Global, Built-in).
A instrução de atribuição no interior de uma função cria um nome no
espaço dos nomes local, pelo que efectuar uma atribuição a uma
variável do módulo principal no interior de uma função não tem o
efeito esperado! Quando se pretende “alterar” um nome do espaço
global é necessário declará-lo como global no interior da função da
seguinte forma:
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
z = 22
def addanumber(x):
global z
z = z + x
addanumber(1)
print z
23
Deve-se evitar, sempre que possı́vel a alteração de variáveis globais
no interior de funções. De uma forma mais elegante, o programa
anterior seria escrito assim:
>>>
>>>
>>>
>>>
>>>
>>>
>>>
z = 22
def addanumber(x):
return z + x
z = addanumber(1)
print z
23
Agora a alteração do valor da variável z é delegado para o programa
principal, de forma explı́cita. Trata-se de uma função segura, sem
efeitos laterais.
Não é necessário utilizar a declaração global para podermos aceder
ao valor de z no interior da função. Seguindo a regra LGB o nome é
considerado como global. Uma vez que não lhe é atribuı́do um valor
no interior da função, z não existe no espaço de nomes local da
função!
Por outro lado, a atribuição de um nome a um objecto no interior de
uma função transforma o nome em local, a menos que ele seja
explicitamente declarado global. Isto é assim mesmo que o nome
exista já no espaço global (nomes idênticos podem existir em espaços
diferentes, mas não têm qualquer relação entre si).
Vejamos o que acontece na primeira versão do programa se omitirmos
a declaração global. . .
>>>
>>>
>>>
>>>
>>>
>>>
z = 22
def addanumber(x):
z = z + x
addanumber(1)
UnboundLocalError: local variable ‘z’
referenced before assignement.
A instrução de erro é bem explı́cita. Seguindo a regra LGB, z é uma
variável local e não tem nada a ver com a outra variável homónima
definida no programa principal. . .
Nota: A resolução dos nomes locais é feita estaticamente, quando da
“pré-compilação” do código e não quando o programa corre. Isto
explica a seguinte situação, que costuma surpreender os “novatos”:
>>>
>>>
>>>
>>>
>>>
>>>
x = 99
def prt():
print ‘‘The value of x is’’,x
prt()
The value of x is 99
O programa funciona. Mas se lhe acrescentarmos uma linha, deixa de
funcionar. . .
>>>
>>>
>>>
>>>
>>>
>>>
x = 99
def prt():
print ‘‘The value of x is’’,x
x = 88
prt()
UnboundLocalError: local variable ‘x’
Devolução de valores múltiplos
É perfeitamente legı́timo em Python (ao contrário do que acontece
em outras linguagens como o C) que uma função devolva mais de um
objecto. Os objectos que se pretende devolver enumeram-se na
instrução return e são devolvidos empacotados numa tupla.
>>> def multiples(x)
>>>
return x,2*x,3*x,4*x
>>>
>>> multiples(3)
(3,6,9,12)
Para desempacotar os objectos devolvidos utiliza-se a seguinte forma:
>>> x,y,z,t = multiples(3)
>>> print x,y,z,t
>>> 3 6 9 12
A devolução de valores múltiplos é muito útil. Por exemplo, façamos
uma função para trocar os valores de duas variáveis. Uma versão
naı́ve não funciona!
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
def swap(x,y)
temp = x
x = y
y = temp
x = 2; y = 3
swap(x,y)
print x,y
2 3
Consegue explicar porque é que este swap não funciona?
Felizmente, o problema tem uma solução simples:
>>>
>>>
>>>
>>>
>>>
>>>
>>>
def swap(x,y)
return y,x
x = 2; y = 3
x,y = swap(x,y)
print x,y
3 2
Na realidade, embora seja um bom exemplo da devolução de valores
múltiplos, a função swap é inútil porque em Python a troca de duas
variáveis pode fazer-se muito simplesmente numa única linha!
>>>
>>>
>>>
>>>
>>>
>>>
x = 2; y = 3
print x,y
2 3
x,y = y,x
print x,y
3 2
Nota: em Python todas as funções devolvem pelo menos um valor.
No caso de não existir return ou quando se evoca return sem
referência a um objecto, o objecto devolvido pela função é None.
Normalmente, este objecto é simplesmente ignorado quando se
chama a função.
Recursão
Em Python, tal como na maioria das linguagens de programação
modernas, as funções podem ser definidas de forma recursiva (i.e.,
que incluem na própria definição referências à própria função. Por
exemplo, a seguinte função calcula n! de forma recursiva.
>>> def fact(n):
...
if n == 1:
...
return 1
...
else:
...
return n*fact(n-1)
...
>>> fact(3)
6
A sequência de passos que o programa efectua quando executa esta
função é o seguinte:
fact(3) ->
fact(2) ->
fact(1) ->
volta para
fact(2) <fact(3) <-
3*fact(2) (reserva memória no stack)
2*fact(1) (reserva memória no stack)
1
trás:
2*1
3*2*1 = 6
A definição de funções de forma recursiva é expressiva, mas a sua
execução (em termos de tempo de cálculo e, nalguns casos, de gasto
de memória intermédia stack pode ser dispendiosa. Há contudo
algoritmos que podem ser expressos de forma muita simples
recursivamente e que, expressos de outra forma são muito
complicados e nem sempre o tempo de execução é desfavorável.
Processamento genérico de argumentos
É por vezes desejável escrever funções que possam aceitar um
número variável de argumentos. Para o efeito, os sı́mbolos *args e
**keyw utilizados como argumentos de uma função (na sua
definição ou chamada) significam um número arbitrário de
argumentos posicionais (*args) e de argumentos com valor por
defeito (**keyw). No primeiro caso args é uma lista que contém os
argumentos posicionais e no segundo keyw um dicionário contendo
os argumentos e os valores por defeito.
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
def union(*args):
res = []
for seq in args:
for x in seq:
if not x in res:
res.append(x)
return res
a = [1,2,3]; b =[2,7,8]; c =[0,3]
print union(a,b,c)
[1, 2, 3, 7, 8, 0]
L = [a,b,c]
print union(*L)
[1, 2, 3, 7, 8, 0]
Programação funcional
O que é a programação funcional?
A programação funcional é um estilo de programação que dá uma
ênfase especial à utilização de funções. Numa linguagem de
programação puramente funcional, qualquer instrução é uma função
pura e um programa é uma sequência de funções puras. Uma função
pura é uma função que não altera o valor dos seus argumentos.
Nota: em Python as funções não são necessáriamente puras uma vez que um
objecto mutável passado como argumento de uma função pode ser alterado
no interior da função.
• Função pura: def square(x):
return x*x
• Função impura: def sort(L): L.sort()
Tal como nas linguagens de programação funcional, em Python as
funções são objectos de primeira categoria, o que significa que podem
ser passados como argumentos de funções e serem devolvidos por
funções. Também, como qualquer outro tipo de objecto, as funções
podem ser armazenadas em tuplas, listas ou dicionários.
Exemplo:
import math
ftrig = (math.sin,math.cos,math.tan)
for f in ftrig:
print f(math.pi)
Linguagens de programação funcional
O LISP = LISt Processor, que é uma das linguagens de programação
mais antigas é uma linguagem de programação funcional. Um
programa em LISP é um conjunto de funções que operam sobre listas.
O LISP, ou algum dos seus derivados, são bastante usados em alguns
domı́nios especı́ficos da computação, por ex. em Inteligência
Artificial.
Linguagens funcionais descendentes do LISP:
• Haskell
• Scheme
• Erlang (desenvolvido pela Erikson, telemóveis)
Grande parte da teoria da computação está descrita no paradigma da
programação funcional. Existem mais artigos teóricos sobre
computação formulados em LISP do que em C ou em qualquer outra
linguagem!
Vantagens da programação funcional:
• Elegância formal
• Segurança (elimina os “efeitos laterais”)
Desvantagens: ineficiência (em termos de rapidez de computação),
hermiticidade.
Ferramentas de programação funcional
A linguagem de programação Python possui algumas ferramentas de
programacão funcional, correspondentes a cinco funções
pré-definidas:
• filter
• map
• reduce
• lambda
• apply
Utilizando estas ferramentas e algumas das caracterı́sticas intrı́nsecas
do Python, em particular a avaliação em curto-circuito de expressões
lógicas, é possı́vel escrever programas no estilo funcional. Mesmo
quando esse não é o objectivo, as ferramentas de programação
funcional do Python revelam-se muito úteis em vários contextos.
Programação funcional: filter
A função pré-definida filter tem a seguinte sintaxe:
filter(f, L)
onde f é uma função e L uma sequência (tupla, lista ou cadeia de
caracteres).
A função filter devolve um valor que é uma sequência que
contem todos os elementos de L para os quais o resultado da
aplicação da função f tem o valor lógico verdadeiro. No caso de f ser
None, filter devolve uma lista com todos os elementos de L que
têm o valor lógico verdadeiro.
Exemplo:
>>>
>>>
>>>
>>>
>>>
numbers = range(8)
def even(x):
return x % 2 == 0
print filter(even,numbers)
[0, 2, 4, 6]
Exemplo:
>>
>>
>>
>>
>>
>>
bag = [‘a’,2,0,[],’’]
newbag = filter(None,bag)
print bag
[‘a’, 2, 0, [], ’’]
print newbag
[‘a’, 2]
Programação funcional: map
A função pré-definida map tem a seguinte sintaxe:
map(f, L1, L2,...)
onde f é uma função e L1, L2, ... são uma ou mais sequências.
O resultado desta função é uma lista contendo os objectos resultantes
da aplicação de f à (às) lista(listas). Se existirem n listas, f deverá ser
uma função de n argumentos. Se f for None, a lista devolvida é uma
lista dos elementos de L1, ou no caso de haver mais do que uma lista,
devolve uma lista de tuplas, cada tupla contendo um elemento de cada
sequência. No caso de as listas não terem todas o mesmo número de
elementos, os elementos em falta nas listas mais pequenas são
substituı́dos por None.
Exemplo:
>>> city = ‘Coimbra’
>>> print map(None,city)
>>> [‘C’, ‘o’, ‘i’, ‘m’,’b’,’r’,’a’]
Exemplo:
>>
>>
>>
>>
>>
>>
x = [1,2,3]
y = [4,5,6]
def mul(x,y): return x*y
z = map(mul,x,y)
print z
[4,10,18]
Programação funcional: reduce
A função pré-definida reduce tem a seguinte sintaxe:
reduce(f, L, <default>)
Esta instrução devolve um valor que é o resultado da aplicação de
uma função binária a uma sequência L, de acordo com o esquema
seguinte:
f(L[0],L[1]) -> x; f(x,L[2]) -> x;
...f(x,L[-1]) -> retorno
Exemplo:
>> def add(x,y):
>>
return x+y
>> print reduce(add,range(0,5))
>> 10
# 0+1+2+3+4
Nota: pode ser passado a reduce um terceiro parâmetro, opcional,
que é o valor devolvido por defeito no caso de a sequência se
encontrar vazia.
Exemplo:
>>
>>
>>
>>
>>
>>
print reduce(add,range(0,5))
10
print reduce(add,[])
Type error: reduce of empty sequence.
print reduce(add,[],0)
0
Funções anónimas: expressões lambda
Na realidade, lambda tem o valor sintáctico de uma expressão que
devolve um objecto do tipo função à qual pode ser atribuı́do
(opcionalmente) un nome. Muitas vezes o objecto devolvido pela
expressão lambda permanece anónimo.
Exemplo:
Calcular a soma dos números de uma lista (L):
def add(x,y):
return x+y
print reduce(add, L)
ou, utilizando a expressão lambda,
add = lambda x,y: x+y
print reduce(add, L)
ou ainda (numa só linha!)
print reduce(lambda x,y: x+y, L)
Note-se que não é permitido escrever (porquê?):
print reduce(def add(x,y): return x+y: x+y, L)
Sintacticamente uma expressão lambda tem a forma:
lambda args:
expr
onde args são os argumentos da função (separados por vı́rgulas) e
expr uma expressão que define a função.
Vantagens e desvantagens do uso de lambda:
• Pode ser utilizada em situações onde sintacticamente é exigida
uma expressão (+)
• Uma função anónima é usada onde é preciso e de seguida
descartada, não ficando a poluir o espaço dos nomes (+)
• Está restringida a funções simples (é uma expressão!), pelo que
não se podem incluir na definição da função estruturas de
controle (if, for, while . . . ) (−).
Na maior parte dos casos a utlização de lambda em vez de def é
uma pura questão de estilo. A utilização de lambda permite escrever
programas mais compactos, mas também se pode argumentar que
ficam menos legı́veis!
É capaz de descobrir o que fazem as funções criadas pelas seguintes
expressões lambda?
lambda a,b: not b and a or gcd(b, a % b)
lambda L: reduce(lambda a,b: gcd(a,b), L)
lambda n: map(lambda x,f=lambda x,f:(x<=1)\
or (f(x-1,f)+f(x-2,f)):f(x,f),range(n))
lambda x,y: (x < y and [x] or [y])[0]
Abrangências de listas
Existe uma forma simples de criar listas sem recorrer sem recorrer às
ferramentas de programação funcional e que pode emular algumas
das funções de map, filter e/ou lambda. Trata-se das abrangências de
listas, que são listas criadas por expressões contendo uma instrução
for, eventualmente seguida de uma ou mais instruções for ou if.
Exemplos:
>>> names = [’ana matos’,’pedro santos’]
>>> [name.title() for name in names]
[’Ana Matos’, ’Pedro Santos’]
>>> vec = [2, 4, 6]
>>> [3*x for x in vec]
[6, 12, 18]
>>> [3*x for x in vec if x > 3]
[12, 18]
>>> [3*x for x in vec if x < 2]
[]
>>> [[x,x**2] for x in vec]
[[2, 4], [4, 16], [6, 36]]
>>> [x, x**2 for x in vec] # error:() required
File "<stdin>", line 1, in ?
[x, x**2 for x in vec]
ˆ
SyntaxError: invalid syntax
>>> [(x, x**2) for x in vec]
[(2, 4), (4, 16), (6, 36)]
Abrangências de listas (cont.)
Mais alguns exemplos:
>>> vec1 = [2, 4, 6]
>>> vec2 = [4, 3, -9]
>>> [x*y for x in vec1 for y in vec2]
[8, 6, -18, 16, 12, -36, 24, 18, -54]
>>> [x+y for x in vec1 for y in vec2]
[6, 5, -7, 8, 7, -5, 10, 9, -3]
>>> [vec1[i]*vec2[i] for i in range(len(vec1))]
[8, 12, -54]
>>> n = 123456
>>> [int(i) for i in str(n)]
[1, 2, 3, 4, 5, 6]
>>> sum([int(i) for i in str(n)])
21
>>> hexa = [’AF’,’12CF’]
>>> [int(n,16) for n in hexa]
[175, 4815]
>>> message = ’Hello, world. What a nice day!’
>>> [word.strip(’,.?!’) \
for word in message.split()]
[’Hello’, ’world’, ’What’, ’a’, ’nice’, ’day’]
Abrangências de listas vs. programação funcional
Suponhamos que pretendı́amos criar uma lista com o quadrado de
todos os números inteiros pares inferiores a 10. Utilizando
abrangências,
>L = [x**2 for x in range(10) if x % 2 == 0]
Utilizando as ferramentas de programação funcional,
>L = map(lambda x, x*x,\
>
filter(lambda x: x % 2 == 0,range(10))
Qual das duas instruções é mais clara? Sem dúvida, a abrangência é
mais legı́vel! No entanto, os dois pedaços de código não são
equivalentes, embora produzam correctamente a mesma lista de
números: [0, 4, 16, 36, 64].
Exemplo:
>x = 123
>L = [x**2 for x in range(10) if x % 2 == 0]
>print L,x
>[0, 4, 16, 36, 64] 9
>x = 123
>L = map(lambda x: x*x,\
>
filter(lambda x: x % 2 == 0,range(10))
>print L,x
>[0, 4, 16, 36, 64] 123
O valor de x foi alterado no primeiro caso, como efeito lateral do
cálculo da abrangência o que pode ser perigoso, se não estivermos a
contar com este comportamento!
As ferramentas de programação funcional não têm efeitos laterais,
pelo que a variável x é apenas e efectivamente uma variável auxiliar,
cuja existência não colide com a de outra variável com o mesmo
nome no exterior da função.
Na tentativa de eliminar os efeitos laterais, as linguagens funcionais
puras traduzem as estruturas de controle habituais (if, for,
while) por funções. Vejamos um exemplo em Python. O pedaço de
código seguinte
if x < 0:
print "Negative number"
else:
print math.sqrt(x)
print x(2),x(-1)
tem a seguinte transcrição funcional:
sqroot = lambda x: x < 0 and
or math.sqrt(x)
print sqroot(2),sqroot(-1)
"Negative number"\
Consegue perceber como é que a função sqroot funciona emulando
o bloco if?
Módulos
Cada ficheiro de um programa python é um módulo com um espaço
de nomes próprio. Este nomes podem ser importados para o espaço
dos nomes de outro programa através da instrução import que tem
duas variantes sintáticas:
import module e from module import obj
No primeiro caso todos os objectos definidos no módulo ficam
disponı́veis por qualificação do nome do módulo; no segundo, o
objecto importado fica disponı́vel através da simples invocação do seu
nome. Exemplo:
import math
print math.cos(math.pi)
ou
from math import cos
print cos(pi)
Para importar todos os objectos definidos num módulo podemos
escrever from module import *.
Existe ainda a possibilidade de importar um objecto definido num
módulo criando um nome alternativo no espaço dos nomes local:
from module import name1 as name2
Exemplo:
from math import cos as coseno
from math import pi
print coseno(pi)
Esta opção é útil quando a função a importar tem o mesmo nome de
uma outra já definida no programa importador. Para evitar poluir o
espaço dos nomes, a forma from module import * deve ser
evitada.
Cada módulo tem um nome, acessı́vel através do identificador
reservado name . O módulo do programa principal tem o nome
especial main . É vulgar encontrar a construção:
if __name__ == "__main__":
instructions
....
As instruções do bloco if só são processadas quando o ficheiro é
executado como programa principal; quando é importado como um
módulo são ignoradas, permanecendo invisı́veis ao programa
importador.
Vantagens da utilização de módulos:
• Reutilização de código
• Partição do espaço dos nomes
• Partilha de funções e estruturas de dados entre vários
componentes de um programa
A caracterı́stica modular do Python é particularmente apreciada em
grandes projectos de software, tendo sido inspirada na linguagem de
programação MODULA, um sucessor do PASCAL.
Ferramentas de introspecção: metaprogramação
Os objectos definidos num módulo podem ser listados com a
instrução dir(module), tal como os atributos de qualquer objecto
podem ser listados com dir(obj). O tipo e endereço de um objecto
na mémória são dados pelas funções type(obj) e id(obj). O
Python possui várias outras ferramentes de introspecção, mas uma
das mais importantes é o atributo doc de cada objecto, que é
suposto conter uma sequência de caracteres com uma descrição
sumária do objecto – a string de documentação, ou “doc string”.
Os módulos, funções e classes definidas pelo utilizador devem
também ser documentadas com uma “doc string”.
As strings de documentação são colocadas no inı́cio dos módulos e
imediatamente a seguir à instrução de definição das funções e classes.
Exemplo:
def gcd(a,b):
"""
Greatest common divider between a and b.
Uses Euclide’s algorithm.
"""
while b:
a,b = b, a % b
return a
print gcd.__doc__
Greatest common divider between a and b.
Uses Euclide’s algorithm.
Programação por objectos
Este capı́tulo está a ser reescrito e será disponibilizado em breve!
Iteradores
As sequências, os dicionários e os ficheiros são objectos iteráveis, aos
quais é possı́vel aceder, elemento a elemento, utilizando uma sintaxe
comum:
for x in obj:
print x
A instrução for x in obj chama a função iter(obj) que cria
um iterador para o objecto obj. Cada um dos elementos é obtido
chamando it.next() onde it é o iterador do objecto. Quando se
esgotam os elementos do objecto é levantada uma excepção que
indica ao ciclo for o fim da iteração.
>>> it = iter(’abc’)
>>> it
<iterator object at 0x00A1DB50>
>>> it.next()
’a’
>>> it.next()
’b’
>>> it.next()
’c’
>>> it.next()
Traceback (most recent call last):
File "<pyshell#6>", line 1, in -toplevelit.next()
StopIteration
Iteradores
É muito fácil tornar uma classe de objectos iterável. Basta para isso
que a classe defina o método iter que deverá devolver um
objecto (o iterador) que por sua vez deverá definir o método next.
Se next for definido na própria classe, basta ao método iter
devolver a instância (self ). Exemplo:
>>> class Reverse:
"Iterator for looping a sequence backwards"
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def next(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
>>> for char in Reverse(’spam’):
print char
m
a
p
s
Geradores
Um gerador é qualquer função que contenha a palavra chave yield,
utilizada para devolver valores à semelhança de return. Estas
funções especiais implementam automaticamente o método next().
Quando se chama next() a função retoma a execução na instrução que
se segue a yield (o gerador lembra-se exactamente do ponto onde
saiu e os valores de todas as variáveis locais).
>>> def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
>>> g = reverse((1,2,3))
>>> g.next()
>>> 3
>>> g.next()
>>> 2
>>> g.next()
>>> 1
>>> g.next()
Traceback (most recent call last):
File "<stdin>", line 1, in ?: StopIteration
>>> for char in reverse(’golf’):
print char
f
l
o
g
• Na maioria dos casos os geradores são a forma mais simples de
implementar um iterador.
• Os geradores guardam memória das variáveis locais e do estado
de execução entre as chamadas da função. Esta caracterı́stica
fundamental, podendo também ser útil noutras situações,
simplifica muito a escrita de um iterador!
• Um gerador invoca automaticamente a excepção
StopIteration que assinala o fim da iteração.
• O conceito de gerador é central na definição de alguma
linguagens de programação como o “Icon”. Iteradores e
geradores são ferramentas poderosas do Python inspiradas nesta
linguagem.
• Geradores recursivos são muitas vezes utilizados para a resolução
de problemas de Inteligência Artificial. Alguns problemas de
enunciado simples, mas que desafiam resolução analı́tica, podem
ser resolvidos por computador usando algoritmos simples, mas
eficazes, baseados nestas ferramentas.
Alguns exemplos clássicos (problemas de xadrêz):
– Problema das oito raı́nhas
– Problema da volta do cavalo
Excepções
Os erros de sintaxe são normalmente detectados pelo compilador na
fase de conversão do programa para bytecode ou na fase de depuração
do programa. Contudo, existe um outro tipo de erros que podem
manifestar-se na execução do programa mesmo quando este é
sintacticamente correcto. A este tipo de erros damos o nome de
excepções.
Alguns exemplos de acções que quando intentadas no decorrer de um
programa dão origem a uma excepção:
• Dividir um número por zero.
• Aceder a um elemento inexistente de uma lista.
• Abir um ficheiro inexistente.
• Converter num número uma string que não representa um
número.
• Iterar um objecto não iterável
• ...
O interpretador de Python detecta a ocorrência de excepções, dando
ao programa a oportunidade de corrigir o erro. Assim, podemos
escrever programas robustos que não ”estouram”mesmo quando
ocorrem situações...excepcionais.
Excepções
As excepções são objectos do tipo classe. Quando ocorre uma
excepção, é instanciada a classe que define a excepção. Esta operação
é detectada pelo interpretador, havendo a possibilidade de processar a
excepção com a instrução try...except. Se o programa não
tratar a excepção, o interpretador abortará o programa com uma
mensagem de erro.
Sintaxe da instrução try...except:
try:
....
except <ExceptionClass>:
....
<else>:
....
<finally>:
....
As cláusulas else e finally são opcionais. O bloco de código
indentado por else é executado se e só se não ocorrer a excepção. O
bloco de código indentado por finally é sempre executado, quer
tenha ocorrido, ou não, uma excepção. O nome da excepção que se
pretende “apanhar” e processar também é opcional. Se não for
indicado, todas as excepções serão apanhadas. A cláusula except
pode ainda tomar as seguintes formas:
except (ExceptionClass1, ExceptionClass2,..)
except ExceptionClass, instance
No primeiro caso (notar os parêntesis obrigatórios) serão apanhadas
todas as excepções enumeradas na tupla. No segundo caso, é passada
uma instância da classe que pode conter informação adicional sobre a
excepção (ver adiante).
Nalguns casos, é útil que o próprio código levante explicitamente
excepções, utilizando a instrução raise ExceptionClass.
Vejamos um exemplo do uso da excepção pré-definida IndexError
que é accionada quando se tenta aceder a um elemento de uma
sequência com um ı́ndice inexistente:
def getn(L,n):
print L[n]
L = [’a’,’b’,’c’]
while 1:
try:
n = input(’Item? ’)
getn(L,n)
except IndexError:
print "Bad index. Try again."
else:
break
Na página seguinte dá-se a lista de todas as excepções pré-definidas e
a sua organização hierárquica. O utilizador pode também definir
novas excepções e levantar excepções.
Hierarquia das Excepções
Exception
+-- SystemExit
+-- StopIteration
+-- StandardError
|
+-- KeyboardInterrupt
|
+-- ImportError
|
+-- EnvironmentError
|
|
+-- IOError
|
|
+-- OSError
|
|
+-- WindowsError
|
+-- EOFError
|
+-- RuntimeError
|
|
+-- NotImplementedError
|
+-- NameError
|
|
+-- UnboundLocalError
|
+-- AttributeError
|
+-- SyntaxError
|
|
+-- IndentationError
|
|
+-- TabError
|
+-- TypeError
|
+-- AssertionError
|
+-- LookupError
|
|
+-- IndexError
|
|
+-- KeyError
|
+-- ArithmeticError
|
|
+-- OverflowError
|
|
+-- ZeroDivisionError
|
|
+-- FloatingPointError
|
+-- ValueError
|
|
+-- UnicodeError
|
|
+-- UnicodeEncodeError
|
|
+-- UnicodeDecodeError
|
|
+-- UnicodeTranslateError
|
+-- ReferenceError
|
+-- SystemError
|
+-- MemoryError
+---Warning
+-- UserWarning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- SyntaxWarning
+-- OverflowWarning
+-- RuntimeWarning
+-- FutureWarning
Verifica-se que IndexError é uma subclasse de LookupError
que por sua vez é uma subclasse de StandardError que deriva da
classe mãe Exception.
De acordo com a hierarquia das excepções, a instrução:
try:
....
except LookupError
...
irá “apanhar” os erros de indexação (de sequências) e os erros de
chave inexistente num dicionário, ao passo que
try:
....
except IndexError
....
“apanhará” exclusivamente os erros de indexação.
As excepções não servem apenas para assinalar e processar erros. Por
vezes, são utilizadas para notificar eventos ou para assinalar situações
excepcionais que exijam uma atenção “especial” e acção imediata.
Exemplo:
def search(word,file):
for line in file:
if word in line: raise ItemFound
try:
search(’pyhton’,myfile)
except ItemFound:
print "Item found."
Excepções definidas pelo programador
É muito fácil definir novas excepções. As excepções são classes que
herdam da classe mãe Exception. Assim, para criar a nova
excepção MyException basta criar esta nova classe e invocá-la
com a instrução raise:
class MyException(Exception):
pass
try:
....
except:
raise MyException
É possı́vel criar excepções mais sofisticadas. Vamos dar como
exemplo a criação de uma excepção NonPositiveNumber como
“guardião” do cálculo de raı́zes quadradas. O próprio número faltoso
e uma mensagem de erro são guardadas nas instâncias da classe (cada
vez que é criada uma excepção a classe é instanciada).
class NonPositiveNumber(Exception):
def __init__(self,x):
self.x = x
self.mesg = "is negative!"
def my_sqrt(x):
from math import sqrt
if x <0 : raise NonPositiveNumber(x)
return sqrt(x)
while 1:
try:
x = input("Number? ")
print my_sqrt(x)
except NonPositiveNumber, error:
print "Bad value: ", error.x, error.mesg
except EOFError:
break
Neste exemplo, error é a instância da classe
NonPositiveNumber criada quando é levantada a excepção. O
nome escolhido para a instância é arbitrário. Para aceder a essa
instância basta utilizar a sintaxe:
except ExceptionClass, instance
em lugar da forma mais simples e vulgar:
except ExceptionClass
A excepção EOFError é utilizada para detectar o fim do input de um
ficheiro. Quando os dados estão a ser inseridos pelo teclado, o fim
dos input é habitualmente assinalado premindo a combinação de
teclas (CTRL+d) que levanta a referida excepção.
Depuração de código
Uma maneira simples de efectuar testes para potenciais situaç ões de
erro no interior de um programa consiste na utlização da instrução
assert, que tem a seguinte sintaxe:
assert <test>, <message>
e que equivale a:
if not test:
raise AssertionError, message
Uma exemplo simples de utilização:
def days_of_month(month):
assert 1 < month < 12, \
"Error: month must be 1..12."
...
Os testes assert são muito úteis quando se procede ao
depuramento de código (debug . As mensagens de erro incluem
automaticamente alguma informação sobre a parte do código que
desencadeou a excepção. Estes testes não serão efectuados,
acelerando a execução de um programa já depurado, quando se
invoca o interpretador de python com a opção -O:
> python -O myprog.py.
Ficheiros
Os ficheiros permitem guardar objectos de forma permanente.
O Python tem um conjunto rico de instruções para manipular
ficheiros.
Um objecto do tipo ficheiro é criado com a instrução open:
f = open(filename,mode)
onde filename é o nome do ficheiro (ex. ’report.txt’), e
mode uma das alternativas ’r’,’w’,’a’,’r+’ que significam
abertura para leitura, escrita, continuação (append) e leitura e escrita.
Um ficheiro é um objecto iterável onde o iterador devolve, em
sequência, cada linha do ficheiro. Uma vez aberto um ficheiro pode
ser lido da seguinte forma:
for line in f:
print line
Os ficheiros possuem vários métodos (fazer dir(f) , onde f é um
objecto do tipo ficheiro dá a lista de todos os métodos). Os mais
importantes são read, readline, readlines e write.
f.read(n) lê n caracteres do ficheiro devolvendo-os numa string.
Se n for omitido, todo o ficheiro será lido. Se o final do ficheiro for
encontrado,o número de caracteres devolvido poderá ser,
eventualmente, inferior ao pedido.
O seguinte programa imprime todo o conteúdo do ficheiro
’report.txt’.
f = open(’report.txt’,’r’)
print f.read()
f.readlines(n) lé n linhas do ficheiro devolvendo-as numa lista
de strings (uma por linha).
lines = f.readlines()
for line in lines:
print line
Os métodos write e writelines permitem escrever uma os mais
linhas num ficheiro. O argumento é, no primeiro caso, uma string, no
segundo uma lista de strings. Exemplo:
f.write(’Hello world!\n’)
messages = [’How are you?,’And your wife?’]
f.writelines(messages)
Notar que apenas strings podem ser guardadas em ficheiros. Objectos
de outro tipo têm primeiro de ser convertidos em strings antes de
poderem ser guardados num ficheiro. Objectos simples podem ser
convertidos com a função str ou o operador de formatação (%).
s = str(123.2)
n = 123.2; s = 189
s = "%10.2f %d" % (f,n)
Objectos mais complexos podem ser serializados automaticamente
utilizando o módulo pickle.
Objectos persistentes: pickle and shelve
Um objecto x pode ser guardado num ficheiro f, aberto para escrita
usando o seguinte método do módulo pickle:
pickle.dump(x)
Para ler de novo o objecto do ficheiro basta fazer
x = pickle.load(f)
A serialização (conversão para string) dos objectos é feita
automaticamente, bem como a sua reconversão no tipo original.
Praticamente qualquer objecto pode ser guardado num ficheiro.
Contudo há que ter o cuidado de carregar os objectos do ficheiro
exactamente pela mesma ordem em que foram guardados!
Esta restrição é levantada no módulo shelve. Utilizando este
módulo podemos guardar os nossos objectos num ficheiro que
funciona como base de dados onde os objectos são indexados por
uma string única a cada objecto. A interface de utilização desta base
de dados assemelha-se à de um dicionário, como mostra o exemplo
seguinte.
O módulo shelve permite implementar muito facilmente uma base
de dados sofisticada.
import shelve
db = shelve.open(’myfile’,’n’) # n = new file
x,y,z,d = 123, ’abc’, range(20), {1:’a’,2:’bbbb’}
class C:
def __init__(self,x):
self.x = x
self.x2 = x**2
def __str__(self):
return "C instance> %d %d" % (self.x,self.x2)
if ___name__ == "__main__":
c = C(123)
db[’x’] = x
db[’y’] = y
db[’z’] = z
db[’d’] = d
db[’c instance’]= c
db[’C class’] = C
db.close()
db = shelve.open(’myfile’,’c’) # c = append data
print db[’d’],db[’x’]
db.close()
Redirecção de input e output
Por vezes é útil redireccionar a saı́da de um programa do écrã para um
ficheiro. Também pode ser útil que um programa passe a ler os dados
que normalmente entram pelo teclado a partir de um ficheiro. Se o
programa correr no sistema operativo Linux (ou num sistema Unix) a
redirecção da entrada e saı́da pode fazer-se na linha de comando:
python myprog < input.dat > output.out
Outros sistemas operativos não dispõem desta facilidade. No entanto,
o Python permite fazer a redirecção dos ficheiros standard de entrada,
saı́da e de erro de uma forma portátil. Este ficheiros estão definidos
no módulo sys com os nomes stdin, stdout e stderr. Por
defeito, sys.stdin aponta para o teclado, e sys.stdout e
sys.stderr para o ecrã. A redirecção da entrada e saı́da faz-se
pondo estes nomes a apontar para outros ficheiros.
O pequeno programa da página seguinte mostra um equivalente
portátil da instrução Unix dada acima. Para correr o programa, deve
digitar-se na linha de comando
python myprog input.dat output.out
Os ficheiros de nome “input.dat” e “output.dat” serão atribuı́dos no
inı́cio do programa a sys.stdin e sys.stdout.
Notar que sys.argv é uma lista que contém as strings de evocação
do programa: sys.argv[0] é o nome do programa e
sys.argv[1:] os argumentos do programa, neste casos os nomes
dos ficheiros de input e output.
#!/usr/bin/env python
"""
Example of redirection of input/output.
Dumb program that simply copies the
input file to the output file.
"""
import sys
try:
sys.stdin = open(sys.argv[1],’r’)
except:
print "Error opening input file.",sys.stdin
sys.exit()
try:
sys.stdout = open(sys.argv[2],’w’)
except:
print "Error opening output file",sys.stdout
sys.exit()
for line in sys.stdin:
print line[:-1] #remove last char (CR)
Como não houve redirecção do ficheiro sys.stderr, as mensagens
de erro eventualmente geradas pelo programa serão ainda enviadas
para o ecrã. Para que estas mensagens também sejam enviadas para o
ficheiro de saı́da que atribuı́mos a sys.stdout bastava incluir a
instrução sys.stderr = sys.stdout a seguir à redirecção de
sys.stdout.
Formatação de output
A conversão de objectos em strings, convenientemente formatados, é
facilitada pela utlização do operador de formatação %. Uma string
formatada é produzida com a expressão:
format-string % (o1,o2,o3...)
A format-string é uma string que pode conter códigos de formatação
da tabela seguinte:
%s
string
%c
caracter
%d
número inteiro decimal
%o
numero inteiro octal
%x
número inteiro hexadecimal
%f
número em vı́rgula flutuante
%e
número em vı́rgula flutuante, notação cientı́fica
%g
número em vı́rgula flutuante, formato alternativo
Exemplos:
>>> x = "This is %5.3f with %d decimals" %\
(math.pi,3)
>>> print x
This is 3.142 with 3 decimals

Documentos relacionados