(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.