Introdução à Programação em Python

Notebook 02 - Conceitos básicos da linguagem Python

Carlos Caleiro, Jaime Ramos

Dep. Matemática, IST - 2016

(actualizado em 8 de Fevereiro de 2018)

A linguagem Python

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.

In [1]:
6+7
Out[1]:
13

Valores e tipos

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.

In [2]:
type(1)
Out[2]:
int
In [3]:
id(1)
Out[3]:
4297514912
In [4]:
type(1.0)
Out[4]:
float
In [5]:
id(1.0)
Out[5]:
4391795088

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.

In [5]:
(1==1)
Out[5]:
True
In [6]:
type(1==1)
Out[6]:
bool
In [1]:
1!=3 and (3<1 or (not 0==1))
Out[1]:
True
In [8]:
2 is 1+1
Out[8]:
True
In [9]:
2==2.0
Out[9]:
True

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.

In [10]:
2 is 2.0
Out[10]:
False

O operador isinstance testa a pertença de um valor (objecto) a um tipo (classe).

In [11]:
isinstance(2.0,int)
Out[11]:
False
In [12]:
isinstance(2.0,float)
Out[12]:
True

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}$.

In [13]:
type("bom")
Out[13]:
str
In [6]:
"boma"/2
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-c4ff888eb18b> in <module>()
----> 1 "boma"/2

TypeError: unsupported operand type(s) for /: 'str' and 'int'
In [15]:
"bom"+" "+"dia"
Out[15]:
'bom dia'
In [16]:
"bom dia"[0]
Out[16]:
'b'
In [17]:
"bom dia"[1]
Out[17]:
'o'
In [18]:
"bom dia"[-1]
Out[18]:
'a'
In [19]:
"bom dia"[2:5]
Out[19]:
'm d'
In [20]:
"bom dia"[4:]
Out[20]:
'dia'

O Python disponibiliza também várias funcionalidades que permitem converter valores entre diferentes tipos.

In [21]:
int("523")+1
Out[21]:
524
In [22]:
int(2.9)
Out[22]:
2
In [23]:
float(2)
Out[23]:
2.0
In [24]:
"depois de "+str(123)+" vem "+str(124)
Out[24]:
'depois de 123 vem 124'
In [25]:
chr(123)
Out[25]:
'{'
In [26]:
bool("")
Out[26]:
False
In [27]:
bool("abc")
Out[27]:
True

As cadeias de caracteres podem ainda ser manipuladas recorrendo a métodos específicos, como veremos abaixo.

Expressões

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.

In [28]:
from sympy import *
In [29]:
x,y,z=symbols("x"),symbols("y"),symbols("z")
In [30]:
srepr(x+y*z)
Out[30]:
"Add(Symbol('x'), Mul(Symbol('y'), Symbol('z')))"
In [31]:
srepr((x+y)*z)
Out[31]:
"Mul(Symbol('z'), Add(Symbol('x'), Symbol('y')))"
In [32]:
?sympify

Expressões alternativas

Uma forma particularmente útil de construir expressões, é a composição alternativa.

In [33]:
2 if 1==1 else 3
Out[33]:
2
In [34]:
2 if 1==2 else 3 if 1==1 else 4
Out[34]:
3

Nomes e atribuição

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

In [35]:
import keyword
print(keyword.kwlist)
['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
In [36]:
and=3
  File "<ipython-input-36-8a3b1ae9e2b5>", line 1
    and=3
      ^
SyntaxError: invalid syntax

O mecanismo de associação de um nome a um valor é conhecido por atribuição.

In [37]:
a=5
In [38]:
a+a**2
Out[38]:
30
In [39]:
a
Out[39]:
5
In [40]:
b=a
In [41]:
b
Out[41]:
5
In [42]:
a=a+1
In [43]:
a
Out[43]:
6
In [44]:
b
Out[44]:
5

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.

Instruções

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

Outras instruções básicas

Métodos

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.

In [45]:
bit_length?
Object `bit_length` not found.
In [46]:
help(int.bit_length)
Help on method_descriptor:

bit_length(...)
    int.bit_length() -> int
    
    Number of bits necessary to represent self in binary.
    >>> bin(37)
    '0b100101'
    >>> (37).bit_length()
    6

In [47]:
int.bit_length(7)
Out[47]:
3
In [48]:
(7).
Out[48]:
3
In [7]:
x=8
In [50]:
int.bit_length(x)
Out[50]:
4
In [51]:
x.
Out[51]:
4

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

In [1]:
from IPython.display import Image
Image("tabcomp.png")
Out[1]:

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.

In [53]:
Image("strmethods.png")
Out[53]:

Outra forma expedita de aceder aos métodos disponíveis de um tipo, ou de um objecto desse tipo, consiste em usar dir.

In [2]:
dir(str)
Out[2]:
['__add__',
 '__class__',
 '__contains__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getitem__',
 '__getnewargs__',
 '__gt__',
 '__hash__',
 '__init__',
 '__iter__',
 '__le__',
 '__len__',
 '__lt__',
 '__mod__',
 '__mul__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__rmod__',
 '__rmul__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 'capitalize',
 'casefold',
 'center',
 'count',
 'encode',
 'endswith',
 'expandtabs',
 'find',
 'format',
 'format_map',
 'index',
 'isalnum',
 'isalpha',
 'isdecimal',
 'isdigit',
 'isidentifier',
 'islower',
 'isnumeric',
 'isprintable',
 'isspace',
 'istitle',
 'isupper',
 'join',
 'ljust',
 'lower',
 'lstrip',
 'maketrans',
 'partition',
 'replace',
 'rfind',
 'rindex',
 'rjust',
 'rpartition',
 'rsplit',
 'rstrip',
 'split',
 'splitlines',
 'startswith',
 'strip',
 'swapcase',
 'title',
 'translate',
 'upper',
 'zfill']

Leitura e escrita

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.

In [8]:
input("valor? ")
valor? 67
Out[8]:
'67'
In [9]:
int(input("valor? "))+1
valor? 67
Out[9]:
68
In [56]:
v=input("valor? ")
valor? 8
In [57]:
print("valor =",v,", quadrado =",int(v)**2)
valor = 8 , quadrado = 64

Poderá ser útil saber que a mudança de linha corresponde ao caracter invisível representado por "\n".

In [58]:
print("valor =",v,"\nquadrado =",int(v)**2)
valor = 8 
quadrado = 64

É possível formatar o resultado de um comando print de formas mais sofisticadas, usando o método format.

In [59]:
"conseguir fazer {} dos {} exercícios do teste".format(3,5)
Out[59]:
'conseguir fazer 3 dos 5 exercícios do teste'

Leitura e escrita em ficheiros

É particularmente útil, em muitas situações, criar, ler e/ou escrever em ficheiros. O Python disponibiliza formas expeditas para tal.

In [60]:
%pwd
Out[60]:
'/Users/ccal/Desktop/Dropbox/python'

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

In [62]:
f=open("novo.txt","x")
In [63]:
%ls
HTML.version/
My First Jupyter Notebook.ipynb
NB01 - IPython como ambiente de computação, cálculo e visualização.ipynb
NB02 - Conceitos básicos da linguagem Python.ipynb
NB03 - Listas e outros tipos iteráveis.ipynb
NB04 - (limpar)Programação recursiva.ipynb
NB05 - (limpar)Programação imperativa.ipynb
NB06 - (limpar)Algoritmos de ordenação.ipynb
NB07 - (limpar)Programação funcional e comparação entre paradigmas de programação.ipynb
NB08 - (limpar)Programação em grande escala.ipynb
NB09 - (halfway)Simulação digital estocástica.ipynb
NB10 - (halfway)Programacao orientada a objectos.ipynb
NB11 - (missing)Verificação de programas.ipynb
Untitled.ipynb
animacoes.ipynb
basic_animation.mp4
cartao.ist.pdf
cartao.ist.png
listmethods.png
mymodule.py
new
novo.txt
old/
strmethods.png
tabcomp.png
test.mp4
In [64]:
type(f)
Out[64]:
_io.TextIOWrapper

Uma vez criado e aberto o ficheiro podemos escrever nele o que entendermos, e de seguida fechá-lo.

In [65]:
f.write("bom dia\nboa tarde\nboa noite")
Out[65]:
27
In [66]:
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.

In [67]:
f=open("novo.txt","r")
In [68]:
f.read()
Out[68]:
'bom dia\nboa tarde\nboa noite'
In [69]:
f.read()
Out[69]:
''
In [70]:
f.close()

Podemos também ler o ficheiro linha por linha.

In [71]:
f=open("novo.txt","r")
In [72]:
f.readline()
Out[72]:
'bom dia\n'
In [73]:
f.readline()
Out[73]:
'boa tarde\n'
In [74]:
f.readline()
Out[74]:
'boa noite'
In [75]:
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".

In [76]:
f=open("novo.txt","a")
In [77]:
f.write("\nfim")
Out[77]:
4
In [78]:
f.close()
In [79]:
f=open("novo.txt","r")
In [80]:
f.read()
Out[80]:
'bom dia\nboa tarde\nboa noite\nfim'
In [81]:
f.close()

Instruções compostas

Composição sequencial

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.

In [82]:
x=7;x=8;x=x+1;x
Out[82]:
9

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.

In [83]:
x=5
x=x+1
x
Out[83]:
6

Composição alternativa

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.

In [84]:
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 
Out[84]:
4
In [85]:
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 
Out[85]:
25
In [86]:
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 
Out[86]:
9
In [87]:
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 
Out[87]:
0

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

In [88]:
x=100

if x<50:
    x=x+1

x
Out[88]:
100

Veremos mais tarde os mecanismos de composição iterativa de instruções.

Definição de funções

É frequente querermos definir um procedimento para ser utilizado para diferentes valores, no momento, ou mais tarde.

In [12]:
def escrevepercentagem(tot,val):
    global w
    w=int(100*val/tot)
    print(w,"%")
In [2]:
?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.

In [13]:
escrevepercentagem(25,11)
44 %
In [11]:
w
Out[11]:
44

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.

In [10]:
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.

In [11]:
percentagem(20,14)
Out[11]:
70
In [12]:
x=percentagem(88,20)
x
Out[12]:
22

Naturalmente, uma função já definida pode ser usada na definição de novas funções.

In [17]:
def percentagens(val1,val2,val3):
    return [percentagem(val1,val3),percentagem(val2,val3),percentagem(val1,val2)]
In [18]:
alunos=102
In [19]:
avaliados=78
In [20]:
aprovados=64
In [21]:
percentagens(alunos,avaliados,aprovados)
Out[21]:
[62, 82, 76]
In [100]:
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]))
In [101]:
estatistica([10,20,0,0,9,6,14])
Out[101]:
(11.8, [42, 60, 71])

Comentários

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.

In [102]:
# estas linhas são irrelevantes para o interpretador
# mas poderão ser fundamentais para quem lê o código 

Sumário

  • Python é uma linguagem de programação interpretada e orientada a objectos (OO)
  • Interagimos com o interpretador por execução de instruções, que avaliam expressões e actualizam o contexto da computação
  • Em Python cada expressão tem um tipo que determina as operações que lhe são aplicáveis; números, inteiros ou reais, e valores lógicos correspondem a tipos básicos; os tipos não-básicos são usualmente iteráveis (ou então correspondem a classes de objectos definidas pelo programador)
  • As instruções básicas mais comuns, além da avaliação de expressões, são atribuições de valores a nomes
  • As instruções podem ser compostas sequencialmente, alternativamente ou iterativamente, dando origem a programas mais complexos
  • O objectivo final do programador é definir as funções (ou, mais tarde, métodos em classes) que permitam resolver de forma expedita os problemas que lhe são colocados

Bibliografia

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.