(actualizado em 8 de Fevereiro de 2018)
A linguagem Python, extremamente versátil e cada vez mais utilizada não apenas na academia mas também, e fundamentalmente, na indústria, tem três características fundamentais.
(1) Python é uma linguagem interpretada. Isto significa que funciona num ambiente em que um interpretador interactivo avalia cada expressão no contexto definido, tendo por efeito o cálculo de um resultado (ou uma mensagem de erro) e eventual actualização do contexto. Esta classificação surge por oposição às linguagens ditas compiladas (como por exemplo C, C++ ou Fortran) em que todo um programa é analisado por um compilador que, caso não detecte erros, produz então um executável que pode ser utilizado.
(2) Python é uma linguagem de programação orientada a objectos (OO). Não nos vamos deter demasiado nesta questão, de momento, mas é útil que tenhamos presente que a linguagem Python manipula objectos. Cada objecto é caracterizado por um identificador, um valor corrente, e um menu de métodos e operações que lhe são aplicáveis (e que são determinados pela classe do objecto).
(3) Python é uma linguagem fortemente tipada, o que significa que a aplicação de uma operação a um valor inadequado resulta numa mensagem de erro. O sistema de tipos é dinâmico e os tipos são implícitos, o que facilita bastante o trabalho do programador (que não necessita de declarar o tipo dos objectos que manipula), mas em cada momento cada expressão tem associado um objecto cujo tipo pode ser interpretado como sendo a classe a que pertence. Para certos tipos, ditos imutáveis, o valor que está associado a um objecto é sempre o mesmo. Noutros casos, ditos tipos mutáveis, há métodos disponíveis para alterar o valor associado a um objecto.
A forma como os valores (objectos) são manipulados é definida por intermédio de comandos, que incluem expressões que referem os valores (objectos) em causa.
6+7
Em Python, cada valor (objecto) pertence a um determinado tipo (classe). Esse tipo determina quais as operações que podem ser aplicadas a esse valor (objecto). Trata-se portanto de uma linguagem tipada, mas onde os tipos não são explícitos, o que facilita bastante o trabalho do utilizador (apesar de poder trazer outro tipo de problemas, a jusante, na depuração de erros). Os tipos básicos disponíveis mais úteis são: números inteiros (int
), números reais (float
) e valores lógicos (bool
). Os tipos básicos são imutáveis.
type(1)
id(1)
type(1.0)
id(1.0)
O tipo bool disponibiliza as constantes True e False, bem como diversos operadores lógicos (and
, not
, or
). O Python tem ainda diversos operadores de comparação que devolvem valores lógicos: <
, <=
, >
, >=
, ==
, !=
, is
, is not
, isinstance
.
(1==1)
type(1==1)
1!=3 and (3<1 or (not 0==1))
2 is 1+1
2==2.0
Os operadores lógicos is
e is not
comparam os identificadores dos objectos em causa. Vale a pena compreender a relação de is
com ==
. Obviamente, se duas expressões denotam o mesmo objecto terão também o mesmo valor. O contrário não é verdade em geral, nomeadamente para tipos mutáveis.
2 is 2.0
O operador isinstance
testa a pertença de um valor (objecto) a um tipo (classe).
isinstance(2.0,int)
isinstance(2.0,float)
Além dos tipos básicos, há também os tipos sequenciais, ou iteráveis: cadeias de caracteres (str
), listas (list
), vectores (tuple
), registos (dictionary
), conjuntos (set
), e ficheiros. Cadeias de caracteres, vectores e ficheiros são tipos imutáveis, enquanto listas, registos e conjuntos são tipos mutáveis. É possível ainda introduzir novos tipos, por definição das respectivas classes.
Atentamos de seguida nas cadeias de caracteres, deixando os restantes tipos referidos para mais tarde. Dedicaremos o próximo notebook ao tipo das listas, pela sua importância, o que nos permitirá por um lado compreender melhor a natureza OO da linguagem Python (já que o tipo das listas é mutável), e por outro lado introduzir os conceitos essenciais sobre outros tipos iteráveis. A definição de novas classes enquadra-se na exploração mais a fundo da orientação a objectos, um tópico mais avançado que abordaremos mais tarde.
As cadeias de caracteres, ou $\textit{strings}$, são representadas em Python usando aspas (") ou plicas ('). Estão disponíveis diversas operações para manipular $\textit{strings}$.
type("bom")
"boma"/2
"bom"+" "+"dia"
"bom dia"[0]
"bom dia"[1]
"bom dia"[-1]
"bom dia"[2:5]
"bom dia"[4:]
O Python disponibiliza também várias funcionalidades que permitem converter valores entre diferentes tipos.
int("523")+1
int(2.9)
float(2)
"depois de "+str(123)+" vem "+str(124)
chr(123)
bool("")
bool("abc")
As cadeias de caracteres podem ainda ser manipuladas recorrendo a métodos específicos, como veremos abaixo.
Tal como nos vários exemplos que já vimos, às expressões em Python está associado um valor. As expressões constroem-se a partir de expressões atómicas (literais), e de nomes, por aplicação de operadores e funções. Já vimos uma panóplia de operadores básicos, aritméticos, lógicos e sobre strings. Veremos adiante como podemos programar os nossos próprios operadores. Nas expressões usam-se parênteses para associação, como é usual. A extensão Sympy permite analisar a estrutura de uma expressão.
from sympy import *
x,y,z=symbols("x"),symbols("y"),symbols("z")
srepr(x+y*z)
srepr((x+y)*z)
?sympify
Uma forma particularmente útil de construir expressões, é a composição alternativa.
2 if 1==1 else 3
2 if 1==2 else 3 if 1==1 else 4
É possível, frequente e desejável, associar nomes a valores (objectos), por forma a memorizá-los e ter um mecanismo simples para os referir.
Na terminologia das linguagens de programação é usual chamar variáveis aos nomes. No entanto, usaremos preferencialmente nomes (e não variáveis) pois a linguagem Python dá um uso particular ao mecanismo de associação de valores a nomes que a distingue da maioria das linguagens de programação.
Em Python, um nome é uma qualquer sequência de caracteres (sem aspas, não se trata de uma string), cujo primeiro caracter é tipicamente uma letra, maiúscula ou minúscula. Nomes começados por _ também são possíveis, mas convenciona-se que têm um significado especial, de que falaremos mais tarde. Convenciona-se ainda que nomes cujo primeiro caracter é uma letra maiúscula são usados para definir classes de objectos, algo a que retornaremos daqui a algumas aulas. Para já, portanto, usaremos apenas nomes iniciados com uma letra minúscula.
Note-se que há vários nomes reservados, predefinidos na linguagem Python, que não podemos utilizar, parte dos quais até já conhecemos.
import keyword
print(keyword.kwlist)
and=3
O mecanismo de associação de um nome a um valor é conhecido por atribuição.
a=5
a+a**2
a
b=a
b
a=a+1
a
b
Isto acontece porque o tipo dos objectos que estamos a manipular é imutável. O objecto que representa o valor 5 vai sempre representar o valor 5. A atribuição a=a+1 associa então ao nome a o objecto que corresponde a avaliar 5+1, que representa o valor 6. No entanto, o objecto associado a b não é alterado.
Veremos adiante que o mecanismo de atribuição tem um comportamento menos óbvio quando os valores (objectos) manipulados são de tipos mutáveis, como as listas.
Vale a pena reforçar que os operadores de atribuição (=
) e de comparação (==
) são distintos, e têm papéis muito diferentes, pelo que não devem ser confundidos.
As instruções são a forma como podemos prescrever as acções que queremos efectuar. Instruções básicas podem ser compostas para dar origem a instruções mais complexas. Entre as instruções básicas mais comuns temos a avaliação de expressões e o mecanismo de atribuição, que vimos acima, a invocação de métodos, e instruções de leitura e escrita (input/output).
Cada tipo (classe) de valores (objectos) tem associada uma série de métodos. A invocação desses métodos pode dar origem a um valor, bem como resultar em alterações nos objectos em causa, no caso de serem mutáveis. Os métodos associados a cada tipo (classe) podem ser usados de duas formas.
bit_length?
help(int.bit_length)
int.bit_length(7)
(7).
x=8
int.bit_length(x)
x.
Os métodos associados a cada valor (objecto) podem ser facilmente consultados usando o mecanismo de introspecção do IPython (completação por TAB a partir do objecto, ou do seu tipo).
from IPython.display import Image
Image("tabcomp.png")
Neste caso, trabalhando sobre inteiros, que correspondem a um tipo imutável, a invocação de métodos tem um efeito em tudo semelhante ao do uso de operadores em expressões. Veremos adiante, no contexto de tipos mutáveis, que a situação pode ser um pouco mais complexa.
Relativamente às cadeias de caracteres, ou strings, valerá a pena explorar os métodos disponíveis.
Image("strmethods.png")
Outra forma expedita de aceder aos métodos disponíveis de um tipo, ou de um objecto desse tipo, consiste em usar dir
.
dir(str)
Os mecanismos de leitura e escrita de dados (input/output) são fundamentais para a interacção desejável entre os nossos programas e os seus utilizadores.
input("valor? ")
int(input("valor? "))+1
v=input("valor? ")
print("valor =",v,", quadrado =",int(v)**2)
Poderá ser útil saber que a mudança de linha corresponde ao caracter invisível representado por "\n".
print("valor =",v,"\nquadrado =",int(v)**2)
É possível formatar o resultado de um comando print de formas mais sofisticadas, usando o método format.
"conseguir fazer {} dos {} exercícios do teste".format(3,5)
É particularmente útil, em muitas situações, criar, ler e/ou escrever em ficheiros. O Python disponibiliza formas expeditas para tal.
%pwd
Para criar um ficheiro de texto nesta localização (a que acedemos e podemos alterar usando magias do IPython), basta fazer o seguinte (daria um erro se o ficheiro já existisse).
f=open("novo.txt","x")
%ls
type(f)
Uma vez criado e aberto o ficheiro podemos escrever nele o que entendermos, e de seguida fechá-lo.
f.write("bom dia\nboa tarde\nboa noite")
f.close()
O número 27 que recebemos como resultado da escrita corresponde ao número de caracteres escritos.
Podemos agora ler o conteúdo do ficheiro.
f=open("novo.txt","r")
f.read()
f.read()
f.close()
Podemos também ler o ficheiro linha por linha.
f=open("novo.txt","r")
f.readline()
f.readline()
f.readline()
f.close()
Se abrirmos o ficheiro para escrita (usando "w") iremos reescrevê-lo, perdendo o conteúdo anterior. Caso queiramos escrever no final do ficheiro devemos abri-lo usando "a".
f=open("novo.txt","a")
f.write("\nfim")
f.close()
f=open("novo.txt","r")
f.read()
f.close()
O mais simples dos mecanismos de composição de instruções é a denominada composição sequencial. Consiste, essencialmente, na execução consecutiva de várias instruções, na ordem especificada.
x=7;x=8;x=x+1;x
O valor de x obtido não é surpreendente, se considerarmos que (;) prescreve a execução sequencial das várias atribuições, da esquerda para a direita. Apesar de suportada, e muito comum noutros ambientes, esta sintaxe é altamente desaconselhada em Python, por razões que promovem a legibilidade do código, bem como os princípios essenciais da programação estruturada. A forma quase universalmente usada em Python para expressar a composição sequencial é, pura e simplesmente, a mudança de linha.
x=5
x=x+1
x
De forma semelhante à composição alternativa de expressões, que vimos acima, é também possível, e extremamente útil, a composição alternativa de instruções.
x=2
if x<3:
x=x*2
elif x<5:
x=x+1
x=x**2
elif x<10:
x=x+1
else: x=0
x
x=4
if x<3:
x=x*2
elif x<5:
x=x+1
x=x**2
elif x<10:
x=x+1
else: x=0
x
x=8
if x<3:
x=x*2
elif x<5:
x=x+1
x=x**2
elif x<10:
x=x+1
else: x=0
x
x=100
if x<3:
x=x*2
elif x<5:
x=x+1
x=x**2
elif x<10:
x=x+1
else: x=0
x
Note-se que a indentação (alinhamento dos comandos) é fundamental. Caso as condições não sejam exaustivas e seja omitida a hipótese final (else) não é executada nenhuma instrução (ou é executada a instrução que nada faz, que é designada em Python por pass).
x=100
if x<50:
x=x+1
x
Veremos mais tarde os mecanismos de composição iterativa de instruções.
É frequente querermos definir um procedimento para ser utilizado para diferentes valores, no momento, ou mais tarde.
def escrevepercentagem(tot,val):
global w
w=int(100*val/tot)
print(w,"%")
?escrevepercentagem
A definição acima escreve, como percentagem, a razão entre os parâmetros val e tot. Por si só o procedimento assim definido não devolve nenhum valor. Mas a definição age também por efeito colateral, já que o resultado é guardado no nome w que se considera global.
escrevepercentagem(25,11)
w
Vale a pena retirar a declaração de w é global, para compreender a diferença.
Por vezes, além do mais, queremos devolver explicitamente um valor calculado, caso em que o procedimento se denomina uma função.
def percentagem(tot,val):
return int(100*val/tot)
A definição, por si só, apenas acrescenta uma definição de percentagem ao sistema. A função assim definida pode ser utilizada várias vezes, a partir do momento em que é definida.
percentagem(20,14)
x=percentagem(88,20)
x
Naturalmente, uma função já definida pode ser usada na definição de novas funções.
def percentagens(val1,val2,val3):
return [percentagem(val1,val3),percentagem(val2,val3),percentagem(val1,val2)]
alunos=102
avaliados=78
aprovados=64
percentagens(alunos,avaliados,aprovados)
def estatistica(w):
avals=[x for x in w if x!=0]
return sum(w)/len(avals),percentagens(len(w),len(avals),len([x for x in avals if x>9.4]))
estatistica([10,20,0,0,9,6,14])
Uma das boas práticas de programação consiste em comentar o que escrevemos por forma a explicitar, para nós e principalmente para os outros, o significado de cada pedaço de código (rapidamente perceberemos que o código que escrevemos se torna facilmente incompreensível). Linhas começadas por # são consideradas comentários, e portanto não são avaliadas pelo interpretador.
# estas linhas são irrelevantes para o interpretador
# mas poderão ser fundamentais para quem lê o código
Introdução à Programação em Mathematica (3a edição): J. Carmo, A. Sernadas, C. Sernadas, F. M. Dionísio, C. Caleiro, IST Press, 2014.
Think Python: How to think like a computer scientist: A. Downey, Green Tea Press, 2012.
Introduction to Computation and Programming Using Python (revised and expanded edition): J. V. Guttag, MIT Press, 2013.
The Art of Computer Programming: D. E. Knuth, Addison-Wesley (volumes 1--3, 4A), 1998.
Learning Python (fifth edition): M. Lutz, O'Reilly Media, 2013.
Programação em Python: Introdução à programação utilizando múltiplos paradigmas: J. P. Martins, IST Press, 2015.
Introdução à Programação em MatLab: J. Ramos, A. Sernadas e P. Mateus, DMIST, 2005.
Learning IPython for Interactive Computing and Data Visualization: C. Rossant, Packt Publishing, 2013.
Programação em Mathematica: A. Sernadas, C. Sernadas e J. Ramos, DMIST, 2003.