Capítulo 1 - Introdução Python


Liguagem de programação

Muito pode ser dito sobre as linguagens de programação, a sua história já é longa e rica. Nesta secção vamos restringir a apresentação ao que achamos essencial para enquadrar a linguagem de programação Python no contexto atual do desenvolvimento tecnológico.

Python

A linguagem de programação Python é constituída por diferentes construções sintáticas, uma grande variedade de funções em bibliotecas e estruturas de dados standard à linguagem. Formalmente podemos ignorar grande parte destes atributos, para o tipo de aplicações que temos em mente. Pretendemos implementar simples funções ou pequenos programas com o propósito de resolver problemas de matemática discreta e incentivar o seu estudo mais aprofundado. A complexidade dos problemas serão incrementado progressivamente ao longo dos Capítulos. O que inicialmente são pequenos scripts, com meia dúzia de linhas de programação, nos últimos Capítulos do livro vai exigir a utilização de vários módulos descritos em ficheiros separados. Nesse sentido, a parte inicial, ou nos capítulos iniciais, a execução das linhas de comando faz-se usando Jupyter Notebooks. Na parte final deste Notebook, passa a ser preciso a utilização dum editor de texto ou de um ambiente de programação. Aqui usamos o IDLE que apesar das suas limitações é usado aqui como ambiente de desenvolvimento standard.

Temos no entanto de aprender alguns conceitos de programação básicos antes de podermos resolver qualquer problema. Os exemplos de utilização são apresentados após apresentação de um enquadramento teórico, nos Capítulos seguintes.

-- execução do código --

-- interpretador de Python --

Fundamentos

Um Notebook é uma sequência de células, que aqui são de dois tipos. As células que contém texto, como esta que está a ler, designaremos de células de texto. O espaço abaixo representa uma célula de execução. O conteúdo de ambas as células podem ser alteradas. Pode usar as células de texto para juntar as suas observação ao texto existente. Incentivamos no entanto que experimente com o código oferecido com os nossos exemplos.


In [ ]:

Neste caso a célula está vazia. Com isto quer dizer que não tem código ou programa para ser executado. Para executar uma célula basta que esta esteja selecionada e precionadas simultaniamente das teclas Shift+Return.

A execução de uma célula no Jupyter invoca a execução do código pelo interpretador de Python. Para ver isso podemos começar por usar uma célula para fazer um pouco de aritmética.


In [1]:
2+3


Out[1]:
5

Resumino: o resultado de avaliar 2+3 é 5. Fantástico. Ou seja o código 2+3 é interpretado pelo interpretador de Python como sendo o valor numérico 6.

As células são numeradas à medida que são executadas. Por In[1] o sistema identifica a primeira linha de código executado (O primeiro input). Por Out[1] o sistema identifica o resultado ou interpretação (ou output) de interpretar o primeiro input.

Aqui usamos, como exemplo, o símbolo clássico + para representar a operação de adição. Em Python usamos também - e / para representar a operação de subração e de divisão, respetivamente. Outras operações têm representações menos imediatas. Por exemplo usamos 2*3 para descrever $2\times 3$ (o produto de 2 por 3) e 2**3 para identificar $2^3$ (dois ao cubo). Neste caso todos os exemplos que são operadores (aritméticos) com dois argumentos numéricos. Em 2+3 o operador tem por argumento os números 2 e 3, neste sentido + é um operador binário, porque usa dois argumentos.

Experimente alteras o operador usando para isso a sintaxe descrita na tabela que vem abaixo.


In [2]:
2+3


Out[2]:
5
Operador Operação Exemplo Valor ...
** potência 2 ** 3 8
% resto da divisão inteira 22 % 8 6
// quociente da divisão inteira 22 // 8 2
/ divisão 22 / 8 2.75
* multiplicação 3 * 5 15
- subtração 5 - 2 3
+ adição 2 + 2 4

A ordem das operações, também designada de precedência dos operadores aritméticos, usado pelo interpretador de Python é similar ao que é usada na aritmética. Quando escritos em sequência, os operadores ** (potência) é avaliado primeiro; os operadores *, /, //, e % são avaliados a seguir, da esquerda para a direita; por último é avaliado + e - (também da esquerda para a direita). Naturalmente que podemos usar parênteses para super-impor a ordem de avaliação que desejar.

Pelo descrito, a interpretação de 2+2*11//+1 é 5:


In [3]:
2+2*11//8+1


Out[3]:
5

Que tem a mesma interpretação que (2+((2*11)//8))+1, primeiro calculamos 2*11, depois 22//8, de seguida fazemos 2+2, por fim 2+1.


In [4]:
(2+((2*11)//8))+1


Out[4]:
5

A interpretação dum pedaço de código, pelo interpretador, é um processo complexo, que requer duas fases. Numa primeira fase o sistema verifica a sintaxe da expressão. Validando se a sequência de símbolos que constituem as linhas de código, fazem sentido. Na segunda fase, o sistema avalia o código. Genericamente a avaliação dum pedaço de código, leva a uma alteração do estado da memória do computador. Podendo resultar na produção de um resultado, como nos exemplos anteriores.

Por exemplo, na célula abaixo, temos uma sequência de símbolos que descrevem uma expressão aritmética, que é sintaticamente correta:


In [5]:
(5 - 1) * ((7 + 1) / (3 - 1))


Out[5]:
16.0

Na tentativa de avaliar uma expressão, como a anterior, o Python, guiado pela ordem de precedência imposta pelos operadores ou pela parentização, avalia progressivamente sub-fórmulas até obter um valor único.

As regras usadas para formar expressões que podem ser avaliadas ( ou que o interpretador de Python perceba) descrevem a gramática desta linguagem de programação. Neste livro não vamos fazem uma descrição detalhada da gramática da linguagem de programação, no entanto vamos apresentar alguns fragmentos da linguagem. O último Capítulo do livro é dedicado a uma descrição formal do que se entende por gramática de uma linguagem.

Sempre que pedimos ao Python para executar uma instrução desconhecida, ou cuja descrição é incorreta não respeitando a gramática da linguagem Python, é emitida uma mensagem identificando um erro de sintaxe. Levando isto que o código não seja avaliado pelo sistema.


In [6]:
5+


  File "<ipython-input-6-bc8c986ea5b3>", line 1
    5+
      ^
SyntaxError: invalid syntax

In [7]:
42 + 5 + * 2


  File "<ipython-input-7-46dd2e1d2da1>", line 1
    42 + 5 + * 2
             ^
SyntaxError: invalid syntax

Acima são apresentados dois exemplos de expressões que, com os nossos conhecimentos de aritmética, não sabemos avaliar. De forma idêntica não respeitam as regras da linguagem de programação. O Interpretador gera nestes casos um erro. Este tipo de erro é usualmente designados de erros de sintaxe, alertando que o código não pode ser executada.

Os arguementos dos operadores e o resultado de avaliar as fórmulas anteriores são valores numéricos. Estes valore são codificados por cadeias de dígitos, que o sistema (e nós) interpretamos como números. Estas cadeias são em Python designadas de constantes literais.

Constantes Literais

Como exemplos de constantes literais usados acima temos 2 e 3. Mas poderíamos ter usado 32. O mundo não vive apenas de números, temos necessidade de palavras para descrever as coisas. Qualquer cadeia de símbolos como 'Isto é fantástico!', que corresponde a uma sequência de palavras em Português, é considerado também pelo interpretador como uma constante literal. Estas cadeias de símbolos em programação são genericamente designadas de strings, entendidas como sequência de caracteres.

Outros exemplos de constantes literais são 1.23 ou 9.25e-3. Por exemplos de strings temos 'Isto é uma string' ou "É uma string!". Estas identidades dizem-se constantes porque o seu significado não varia com o contexto da interpretação do código. São constantes literais porque devem ser interpretadas à letra (literalmente) pelo ser humano.

Genericamente as constantes literais são classificadas em duas categorias, ou representam números, ou são strings.

Números

Os números em Python podem ser de três tipos: inteiros, ponto flutuante e complexos:

  1. 2 é um exemplo de inteiro, os inteiros são elementos do conjunto dos números inteiros. Existem no entanto limitações à magnitude dos inteiros que se podem usar.

  2. 3.23 e 52.3E-4 são exemplos de números décimais ou no sistema de virgula flutuante (ou floats, para abreviar). No segundo caso o símbolo E indica uma potência de 10. Neste caso, 52.3E-4 representa o número 52.3$\times$ 10$^{-4}$. Para facilitar neste livro este tipo de números será designado de décimal ou float.

  3. (-5+4j) e (2.3 - 4.6j) são exemplos de números complexos, que em Matemática é usual representar por -5+4i e 2.3 - 4.6i.

Aqui não vamos usar números complexos. Recorremos aos números décimais esporadicamente, fundamentalmente em exemplos de aplicação de algumas funções das bibliotecas standard do Python. Apesar de serem elementos fundamental nas áreas da Matemática Aplicada, aqui centramos os exemplos de programação a estructuras criadas com base em números inteiros e strings. Estas estruturas, são mais adiante designadas de Estruturas de Dados.

Strings

[caracteres]

[caracteres Unicode]

Uma string é então uma constante literária definida por uma sequência de símbolos. As stings são usualmente usadas para representar frase que podem suportar símbolos usando o padrão Unicode (como os que são usados na língua Chinesa ou Japonesa).

Dada a importância deste tipo de entidade, em Python, as strings podem ser definidas de diferentes maneiras. Todos eles seguem uma estrutura comum: Uma string é uma sequência de símbolos onde a sequência inicial de símbolo identifica o início da string e a sequência final o seu fim. A sequência de símbolo que limita a string pode ser formado por uma única aspa, aspas duplas, ou aspas triplas.

Aspas Unitárias

Uma strings pode ser definida por uma sequência de caracteres delimitada por aspas unitárias (ou apóstrofes) tais como:


In [1]:
'O estudo da lógica remonta à civilização helénica'


Out[1]:
'O estudo da lógica remonta à civilização helénica'

Este é o processo mais comum para as representar. As outras formas de descrever tentam apenas facilitar o trabalho dum programados.

Aspas Duplas

Assim as strings podem também ser definidas usando aspas duplas por exemplo:


In [2]:
"A arte da argumentação levou à morte de Sócrates."


Out[2]:
'A arte da argumentação levou à morte de Sócrates.'

Note que aqui no resultado da interpretação o símbolo " foi transformado numa aspa simples '.

Aspas Triplas

Outra forma a definir strings que ocupam várias linhas é usar aspas triplas (""" ou '''). Um exemplo:


In [9]:
'''A palavra "trivial" tem uma etimologia interessante.
 É a conjugação de "tri" (significando '3') e "via" (significando caminho).
 Originalmente refere-se ao "trivium", as três áreas fundamentais do
 'curriculae': gramática, retórica e lógica.
 Assuntos que se tem de dominar para aceder ao "quadrivium", que
 consiste na aritmética, geometria, música e astronomia.'''


Out[9]:
'A palavra "trivial" tem uma etimologia interessante.\nÉ a conjugação de "tri" (significando \'3\') e "via" (significando caminho).\nOriginalmente refere-se ao "trivium", as três áreas fundamentais do\n\'curriculae\': gramática, retórica e lógica.\nAssuntos que se tem de dominar para aceder ao "quadrivium", que\nconsiste na aritmética, geometria, música e astronomia.'

Note que, ao contrário dos anteriores, este exemplo estende-se por diferentes linhas na célula de execução. No entanto agora o resultado da interpretação a string vem alterada. As aspas e a mudança de linha vêm agora codificados por \' e \n. Podemos entender estas sequências como representações internas de elementos do texto original. São conhecidas por sequências de escape. A sequência \n codifica a mudança de linha. Como o Python representa strings usando as aspas símples, as aspas que aparecem dentro da string são substituídas por \'.

Note que, as aspas simples ou duplas não permitem que a mudança de linha seja reconhecida, por exemplo:


In [4]:
'Isto 
está
mal...'


  File "<ipython-input-4-b16624068c7c>", line 1
    'Isto
          ^
SyntaxError: EOL while scanning string literal

Neste caso o Python tenta encontrar o símbolo de fim de string. Como não o encontra na linha onde a string começou a ser definida, é gera do um erro de sintaxe.

Para recuperar o significado original duma string onde aparecem sequências de escape, temos de introduzir o comando print.

O comando print pode ser entendido como uma função que imprime uma representação legível para o humano, do seu argumento ou argumentos.


In [8]:
print('A palavra "trivial" tem uma etimologia interessante. \nÉ a conjugação de "tri" (significando \'3\') e "via" (significando caminho).\nOriginalmente refere-se ao "trivium", as três áreas fundamentais do\n\'curriculae\': gramática, retórica e lógica.\nAssuntos que se tem de dominar para aceder ao "quadrivium", que\nconsiste na aritmética, geometria, música e astronomia.\n')


A palavra "trivial" tem uma etimologia interessante. 
É a conjugação de "tri" (significando '3') e "via" (significando caminho).
Originalmente refere-se ao "trivium", as três áreas fundamentais do
'curriculae': gramática, retórica e lógica.
Assuntos que se tem de dominar para aceder ao "quadrivium", que
consiste na aritmética, geometria, música e astronomia.

Note que agora, as sequência de escape são representados pelo seu significado. Neste contexto, o texto devolvido não é identificado pelo Jupyter como o resultado da interpretação do código. O texto é agora o resultado de executar o comando print.

Assim podemos produzir o significado original:


In [10]:
print( '''A palavra "trivial" tem uma etimologia interessante.
 É a conjugação de "tri" (significando '3') e "via" (significando caminho).
 Originalmente refere-se ao "trivium", as três áreas fundamentais do
 'curriculae': gramática, retórica e lógica.
 Assuntos que se tem de dominar para aceder ao "quadrivium", que
 consiste na aritmética, geometria, música e astronomia.''')


A palavra "trivial" tem uma etimologia interessante.
 É a conjugação de "tri" (significando '3') e "via" (significando caminho).
 Originalmente refere-se ao "trivium", as três áreas fundamentais do
 'curriculae': gramática, retórica e lógica.
 Assuntos que se tem de dominar para aceder ao "quadrivium", que
 consiste na aritmética, geometria, música e astronomia.

O print é uma função bastante versátil. Sem fugir do pretendido experimente com os exemplos abaixo.


In [11]:
print(2+3)


5

In [12]:
print('A','String','está','partida','!')


A String está partida !

In [14]:
print('2+3 =',2+3)


2+3 = 5

Em cada um dos exemplos, quando a célula é executada, é apresentado uma representação das avaliações dos argumentos da função print. Que, como vimos, pode diferir da representação feita pelo interpretador.

Sequências de Escape

Existem várias sequências de escape, para além das duas que apresentámos anteriormente. Recordando: Para definir uma string que contenha um apóstrofe ('), podemos fazer:


In [15]:
print('Why was logic considered to be fundamental to one\'s education?')


Why was logic considered to be fundamental to one's education?

Só assim o apóstrofe interna não entra em conflito com os delimitadores. Caso contrário temos:


In [16]:
print('Why was logic considered to be fundamental to one's education?')


  File "<ipython-input-16-5fa16817f248>", line 1
    print('Why was logic considered to be fundamental to one's education?')
                                                             ^
SyntaxError: invalid syntax

Caso se escolha outro delimitador de string este problema pode ser ultrapassado.


In [17]:
print("Why was logic considered to be fundamental to one's education?")


Why was logic considered to be fundamental to one's education?

Mas para este delimitador, é agora usada uma sequência de escape para inserir aspas duplas no meio da string. A própria barra invertida só pode ser inserida na string pela sequência de escape $\setminus\setminus$, pois caso contrário é assumida como inicio de uma sequência de escape.


In [18]:
print("--\"\\\"...")


--"\"...

In [20]:
print('A lógica centra-se na razão e na noção de verdade.\nA retórica fundamenta-se em ideias feitas e populistas.')


A lógica centra-se na razão e na noção de verdade.
A retórica fundamenta-se em ideias feitas e populistas.

Existem outras sequências de escape, apresenta-se aqui apenas as mais usadas. Para um descrição sistemática use a documentação do interpretador, em https://www.python.org/.

Num editor, durante a programação, é frequente ter a necessidade de continuar uma string na linha imediatamente abaixo. Para isso é usada uma única barra invertida no fim da linha. Por exemplo para escrever Looking Glass de Lewis Carroll, com um único print, podemos fazer:


In [21]:
print("\"Reciprocamente\", continuou Tweedledee, \
\"Se é assim, ele pode ser, \n \
e se não é, será; mas como não é não se preocupa. \
Isto é lógica.\" ")


"Reciprocamente", continuou Tweedledee, "Se é assim, ele pode ser, 
 e se não é, será; mas como não é não se preocupa. Isto é lógica." 

Se por algum motivo tem necessidade que as sequências de escape, na definição da string, não sejam reescrtitas pelo comando print deve usar como prefixo um r ou um R. Por exemplo, na string anterior:


In [22]:
print(r"\"Reciprocamente\", continuou Tweedledee, \
\"Se é assim, ele pode ser, \n \
e se não é, será; mas como não é não se preocupa. \
Isto é lógica.\" ")


\"Reciprocamente\", continuou Tweedledee, \"Se é assim, ele pode ser, \n e se não é, será; mas como não é não se preocupa. Isto é lógica.\" 

Já apresentamos operadores que podem ser usados para operar valores numéricos. Existem também operadores que podemos usar nas strings.

Concatenação de Literais do Tipo String

Quando numa linha de código duas strings são postas lado a lado, o interpretador faz a sua concatenação, ou seja, cria uma nova string que é formada juntando sucessivamente as várias strings. Por exemplo:


In [24]:
print('Originalmente' ' a lógica lidava' ' com linguagem natural')


Originalmente a lógica lidava com linguagem natural

No entanto esta prática pode-se revelar bastante confusa. De forma mais descritiva pode-se recorrer ao operador +:


In [25]:
print('Seria útil'+' demonstrar a correcção'+' dum argumento.')


Seria útil demonstrar a correcção dum argumento.

Uma forma prática para repetir uma sequência de caracteres é usar o operador *. Apesar de neste contexto o operador continuar a ser binário, o primeiro argumento tem de ser um inteiro e o segundo uma string. Por exemplo para repetir 50 vezes o padrão definido pela string '---...--- ' fazemos:


In [26]:
print(50*'---...--- ')


---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- ---...--- 

Variáveis

Como um mundo de constantes não evolui vamos aqui introduzir a noção de variável. Em si a noção não vai variar muito do que está habituado na Matemática (as variáveis duma equação). Em programação uma variável só pode ser usada tiver associado um valor.


In [28]:
x=2
y=3
print(x+y)


5

Aqui podemos entender o anterior programa, como tendo três linhas de código. Na primeira linha definimos x como sendo 2. Na segunda linha definimos y como sendo 3. Na terceira linha imprimimos o valor da interpretação de x+y.

Na maioria das linguagens de programação a descrição acima é suficiente para se entender as linhas de código. No entanto em Python devíamos dizer que com x=2 queremos que x referência a constante literal 2. Neste sentido x+y opera as constantes referenciadas por x e y. O operador = é designado de operador de atribuição. Devemos no entanto notar que, = em programação, não têm exatamente o mesmo sentido que está habituado em Matemática. Em matemática = significa igualdade ou seja é uma relação binária. É isto que lhe permite dizer que 2=1 é falso. Contudo quando escreve x=2, numa equação quer dizer que, assumindo a relação verdadeira, então o valor da variável x é igual a 2. O que se aproxima do significado de uma atribuição numa linguagem de programação.

Executando a próxima célula:


In [30]:
x+z


---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-30-b912837e1c67> in <module>()
----> 1 x+z

NameError: name 'z' is not defined

Como indicado na mensagem de erro, não pode avaliar a expressão porque z não está definido. O interpretador reconhece x, porque foi definido na célula anterior, mas z é a primeira vez que é usado.

Muito Importante: Sempre que define uma variável esta fica disponível na execução de qualquer célula que definir posteriormente.


In [31]:
x+2


Out[31]:
4

In [32]:
3+y


Out[32]:
6

As variáveis podem assim ser usadas para referenciar qualquer constante literal. Facilitando o seu tratamento e manipulação.

De forma genérica, aqui, uma variável pode ser entendida como uma referência a uma parte da memória do computador onde está armazenada informação. Diferindo das constantes literais, uma vez que o seu significado pode variar no decurso da execução dum programa.


In [33]:
x=100
print(x+y)


103

A partir de agora x passa a referenciar a constante literal 100, deixando de referenciar 2.


In [34]:
H1 = 'Seria útil '
H2 = 'demonstrar a correcção '
H3 = 'dum argumento.'
print(H1+H2+H3)


Seria útil demonstrar a correcção dum argumento.

No exemplo H1, H2 e H3 são usadas para identificar três strings, cuja concatenação é imprimida na shell través do comando print.

Em Python as coisas que estão armazenadas em memória e se podem referenciar por variáveis são designados de objectos. Assim, os números e as strings objetos. Os números são objetos numéricos e podem ser de três tipos inteiros, floats ou complexos. A strings são então objetos de tipo string.

O nome dado a uma variáveis é conhecido como identificador, uma vez que é o que permite identificar um objecto em memória.

Em toda a cadeia de carateres pode ser usada como identificador. Existem regras para a sua formação. São elas:

  1. O primeiro carater do identificador tem de ser uma letra do alfabeto (maiúsculo ASCII ou minúsculo ASCII) ou um '_'.
  2. O resto do nome do identificador pode consistir de letras (maiúsculo ASCII ou minúsculo ASCII), '_' ou dígitos (0-9).
  3. Nas letras usadas na construção dum identificador, uma maiúscula é diferente duma minúscula. Por exemplo, myname e myName são identificadores diferentes. Para significar isto é usual escrever que os identificadores são \textit{case-sensitive}.

Exemplos de identificadores válidos são i, __my_name, name_23 e a1b2_c3. Exemplos de identificadores inválidos são 2things e my-name, porque o primeiro caracter do identificador não pode ser um digito e - é um carater que não é permitido ocorer no identificador.


In [1]:
my-name


---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-d3552f9031b3> in <module>()
----> 1 my-name

NameError: name 'my' is not defined

O código da célula acima é entendida pelo interpretador como a subtração dos objetos referenciados por my e name. Como my não está definido é transmitida a mensagem de erro.

Aqui vai o nosso maior programa:


In [35]:
i = 5
print(i) 

i = i + 1
print(i)

s = '''Esta é uma string de múltiplas linhas.
Esta é a segunda linha.'''
print(s)


5
6
Esta é uma string de múltiplas linhas.
Esta é a segunda linha.

No programa começamos por usar o identificador i para referenciar a constante literal 5 através do operador de atribuição (=). Em seguida, imprime-se o valor de i através do comando print.

Na instrução seguinte somamos 1 ao valor referenciado por i. A partir deste momento i passa a referenciar o objeto 6. Em seguida, imprime-se o valor de i, agora 6.

Como já se tinha feito na secção anterior, de forma análoga referencia-se um objeto string pela variável s, que depois se imprime.

Assim o resultado de executar a célula são a impressão de 4 linhas de texto pelos comandos print:

5 6 Esta é uma string de múltiplas linhas. Esta é a segunda linha.

Linhas Lógicas e Físicas

As linhas físicas são aquelas que escrevemos num linha a quando da definição código dum programa. Uma linha lógica é a que o interpretador de Python entende por uma única instrução. O Python implicitamente assume que cada linha física corresponde a uma linha lógica.

Um exemplo de uma linha lógica é uma instrução como:


In [37]:
print('Sócrates é mortal.')


Sócrates é mortal.

Como está escrita numa única linha, é entendida como uma linha física.

Implicitamente, Python incentiva o uso de uma única instrução por linha, com o propósito de tornar o código mais legível.

Se pretende definir mais do que uma linha lógica numa única linha física, então deve separar as linhas lógicas através de um ponto-e-virgula (';') para indicar o fim de cada linha lógica ou instrução. Por exemplo,


In [39]:
i = 5
print(i)


5

é o mesmo que


In [40]:
i = 5; print(i)


5

Voltando a um exemplo anterior:


In [41]:
H=   "\"Reciprocamente\", continuou Tweedledee, \
\"Se é assim, ele pode ser, \n \
e se não é, será; mas como não é não se preocupa. \
Isto é lógica.\" "
print(H)


"Reciprocamente", continuou Tweedledee, "Se é assim, ele pode ser, 
 e se não é, será; mas como não é não se preocupa. Isto é lógica." 

O objeto string que passa a ser referenciado por H deve ser entendido como definido numa única linha lógica, apesar de ocupar diferentes linhas físicas.

Neste sentido usa-se ; (ponto-e-virgula) para separar linhas lógicas na mesma linha física, enquanto $\setminus$ para separar uma linha lógica em diferentes linhas físicas.

Indentação

Os espaços em branco no inicio duma instrução são muito importantes no Python. Na verdade, os espaços brancos no início de uma linha definem a estrutura do programa. Aqui, como é usual em qualquer processador de texto, estes espaços são designados de indentação.

A indentação do código pode ser definido por espaços ou tabulações. Um grupo de linhas consecutivas com a mesma identação é designado por bloco de código. Os blocos de código são usados para indentificar um conjunto de instruções que devem ser executadas, sequencialmente mas em conjunto, condicionada a sua execução por uma condição ou num contexto comum. É óbvio que dito isto assim é muito abstrato. Vamos já a seguir concretizar isto através de blocos controlados por condições. Deixamos os blocos executados no mesmo contexto, para o próximo capítulo.

Note que, um indentação sem nexo leva a erros de sintaxe. Por exemplo:


In [2]:
i = 5
 print('São ', i)
print('São,',i,' os macacos.')


  File "<ipython-input-2-eb76b7b61f72>", line 2
    print('São ', i)
    ^
IndentationError: unexpected indent

Note-se que, existe um espaço simples no início da segunda linha. O interpretador assume que todas as instruções na célula pertencem ao mesmo bloco. Logo o inicio de cada instrução deve estar a primeira coluna, que não é o caso.

Quando e como fazer a indentação, depende da instrução anterior ao bloco. Neste sentido a indentação muitas vezes é automatizada pelo editor. No nosso caso é automatizada pelo Jupyther. O problema é sempre a remoção das indentações, voltar à indentação de blocos anteriores. Esta tem de ser feita pelo programador, sendo determinante para a estrutura, ou seja lógica, do programa.

Operadores e Expressões

Notemos então que a maioria das instruções (linhas lógicas) que escreve contêm expressões. Um exemplo simples de uma expressão é 2+3. Num expressão podemos diferenciar entre operador e argumentos. Os operadores são funções que podem ser identificadas por símbolos, como +, ou palavras especiais. Em Python operadores mesmo estando pré-definidos, podem ser redefinidos pelo programador. Podendo ser sempre classificados segundo o número de argumentos. Dizem-se unários se têm apenas um argumento e binário caso a sua avaliação dependa de dois argumentos.

No exemplo 2+3, temos + como único operador, sendo um operador binário tendo por argumentos as expressões 2 e 3, que são constantes literárias. Como já tínhamos notado a interpretação de + depende do tipo dos objetos que são usados como argumentos. Quando os argumentos são inteiros é usado o algoritmo da soma para somar os valores, devolvendo um inteiro. Como já tínhamos visto, quando os argumentos são duas strings é feita a sua concatenação, devolvendo uma string. Quando os argumentos são números mas pelo menos um deles é um float, é devolvido um float como soma dos dois números.


In [3]:
2.9+3


Out[3]:
5.9

Na tabela abaixo temos os operadores binários usado nestes Notebooks.

Operador Nome Explicação
+ Adição Soma dois objetos
- Subtração Define um número negativo ou a subtração de um número por outro
* Multiplicação Devolve o produto de dois números ou uma string repetida uma certa quantidade de vezes
** Potência Retorna x elevado à potência de y
/ Divisão Divide x por y
// Divisão Inteira Devolve a parte inteira do quociente
\% Modulo Devolve o resto da divisão inteira
< Menor que Compara x a y. Devolvendo True(verdadeiro) se x é menor que y, e False(falso) caso contrário
> Maior que Devolvendo True se x é maior que y, e False caso contrário.
<= Menor ou igual a Devolvendo True se x é menor ou igual a y, e False caso contrário.
>= Maior ou igual a Devolvendo True se x é maior ou igual a y, e False caso contrário.
== Igual a Avalia se os objetos são iguais
!= Diferente de Avalia se os objetos são diferentes
not Operador booleano NOT Se x é True, devolve False. Se x é False, ele devolve True.
and Operador booleano AND x and y devolve False se x é False, senão devolve a avaliação de y.
or Operador booleano OR Se x é True, devolve True, senão devolve a avaliação de y.

Podemos identificar na tabela operadores aritméticos, que podemos usar para fazer "contas" quando os argumentos são números. Mas também existem operadores que usamos para definir condições e o resultado é um valor de verdade. É usual dizermos que 3\<2, é falso. Nestes casos a expressão é avaliada como sendo verdadeira (True) ou falsa (False).


In [1]:
3<2


Out[1]:
False

In [2]:
2<3


Out[2]:
True

O valor lógico destas expressões definem uma nova estrutura de dados, nativa do Python, usualmente designados de Booleanos ou Bool (para variar). A propósito: Sempre que quiser saber qual é o tipo dum objecto pode usar o comando type.


In [3]:
type(1)


Out[3]:
int

In [4]:
type(1.0)


Out[4]:
float

In [5]:
type('uma string')


Out[5]:
str

In [6]:
type(True)


Out[6]:
bool

Temos assim as estruturas fundamentais:

  1. int - os números inteiros
  2. float - os números decimais
  3. str - as cadeias de caracteres ou strings
  4. bool - os valores lógicos ou booleanos

Os valores lógicos True ou False, também são constantes literárias, não nos sendo permitido alterar o seu significado. Sendo elemento fundamental para o controlo do fluxo de execução dum programa.

Estas estruturas são usadas nestes textos como as estruturas de dados primitivas. Vamos usa-las como bloco fundamental para a construtução de estruturas mais complexas.

Controlo do fluxo de programas

Os programas que vimos até aqui, são descritos por uma série de declarações, ficando o interpretador de Python encarregue de as executar seguindo uma ordem pela qual são escritas nas células.

Como alterar o fluxo de execução? Por exemplo, se pretende que o programa tome algumas decisões e faça diferentes coisas em função de diferentes situações. Como imprimir 'Bom Dia' ou 'Boa Tarde', dependendo da hora do dia?

Isto é consumado usando as instruções de controle de fluxo no Python if, for e while, permitindo executar um ou mais blocos de instruções apenas ou enquanto uma condição for verdadeira. Neste Capítulo vamos centrar o nosso esforço no comando if.

Blocos controlados por um if

A instrução if é usada para decidir que bloco de código deve ser executado em seguida. A escolha do bloco a executar é determinado pelo valor lógico de uma condição usado como argumento do comando. Caso a condição seja verdadeira um conjunto de "instruções" é executado em detrimento de outro, que só será executado quando a condição é falsa. Estes blocos executáveis quando a condição é verdadeira é designado de bloco-if (if-block), o bloco que é executado quando a condição é falsa é designado de bloco-else (else-block)).

A sintaxe da instrução é apresentada de seguida. Para isso execute a célula abaixo para diferentes valores de x.


In [7]:
x=2

if x>2:
    print(10*'.^.')
    print('O número é maior que 2.')
    print(10*'\'v\'')
else:
    print(10*'-._')
    print('O número não é maior que 2.')
    print(10*'_.-')


-._-._-._-._-._-._-._-._-._-._
O número não é maior que 2.
_.-_.-_.-_.-_.-_.-_.-_.-_.-_.-

Note que a linha da instrução if termina com ''dois pontos'' indicando que a seguir há um bloco de instruções.

Quando a condição x>2 é verdadeira é executado o bloco-if

    print(10*'.^.')
    print('O número é maior que 2.')
    print(10*'\'v\'')

Sem que a condição x>2 é falsa é executado o bloco-else

    print(10*'-._')
    print('O número não é maior que 2.')
    print(10*'_.-')

Alter o conteúdo da célula acima para ver as alterações do resultado da execução. Podemos assim garantir que o código a executar vai depender do valor que for referenciado pela variável x.

Como indentar os blocos

Não misturar tabulações com espaços a quando da indentação, já que nem todas as plataformas a suportam. É recomendado o uso de uma tabulação, ou dois espaços ou quatro espaços para distinguir cada nível de indentação. Escolha um destes estilos de indentação. Mais importante que a escolha que faz, deve manter-se consistente a ela, ou seja, mantenha apenas um tipo de indentação ao longo de todo o código.

Na maioria dos ambientes de programação, como nos Jupyther Notebooks, o sistema tenta ajudar no processo de indentação. Cabendo ao programador saber quando deve remover a indetação. Note nos exemplos abaixo que a identação determina o fluxo de execução.


In [15]:
x=1

if x>2:
    print(10*'.^.')
    print('O número é maior que 2.')
print(10*'\'v\'')


'v''v''v''v''v''v''v''v''v''v'

Agora só temos um bloco-if com duas instruções. O último print é executado porque a condição só controla a execução das duas intruções que estão no bloco.


In [2]:
x=1

if x>2:
    print(10*'.^.')
print('O número é maior que 2.')
print(10*'\'v\'')


O número é maior que 2.
'v''v''v''v''v''v''v''v''v''v'

Acima a condição só controla a execução do primeiro print. Como a condição é falsa esse print é o único a não ser executado.


In [3]:
x=1

if x>2:
    print(10*'.^.')
    print('O número é maior que 2.')
    print(10*'\'v\'')

Agora, como a condição contínua a ser falsa, as três instruções do bloco não são executadas. Neste caso não é produzido nenhum output.

Um pouco mais abaixo pode encontrar um exemplo mais realista. Chamámos a esse programa "Descobre o número". O que ela faz é referenciar um inteiro e pede ao utilizador para o descobrir. A necessidade de pedir informação para contínuar a execução de um programa exige a introdução duma novas função. Na vedade neste exemplo vai poder facilmente identificar duas novas funções, a utilização de um novo operador relacional e uma nova sintaxe para o comando if. As novas funções são input e int, a nova relação é ==.

A função input tem por argumento uma string e devolve uma string. Isto define a assinatura da função e temos por hábito escrever input:str->str, para significar que entra uma string e sai uma string. A string que serve de argumento é usada como prompt, ou seja título de uma pergunta feita pelo interpretador ao utilizador. A reposta do utilizador deve ser uma sequência de símbolos, que é aceite pelo sistema quando é carregada na tecla de ENTER (ou seja RETURN). Sendo a cadeia transformada numa string, de é devolvida como output pela função:


In [1]:
valor = input("Qual é o seu nome? ")


Qual é o seu nome? Miguel

A variável valor passa a referênciar a cadeia de símbolos que usa para escrever o seu nome.


In [5]:
input("Qual é o número? ")


Qual é o número? 13
Out[5]:
'13'

Apesar de o utilizador escrever uma sequência de digitos, é devolvida a sequência como sendo uma string.

Nestes casos, quando temos de usar um objeto de um tipo como sendo outro, devemos recorrer a uma função de conversão. Para a conversão de objetos para números podemos usar os comandos int ou float. A primeira converte o objeto para um número inteiro. A segunda converte o objeto para um número decimal. Quando os objetos de input são strings a assinatura das funções é int:str->int e float:str->float.


In [11]:
int('0012300')


Out[11]:
12300

In [12]:
float('001.2300')


Out[12]:
1.23

É obvio que uma mensagem de erro é devolvida caso a conversão não possa ser resolvida. Abaixo apresentamos a avaliação da área de um triângulo genérico (aqui tem mesmo de executar a célula para pode ver este comportamento):


In [10]:
h=input('Qual é a altura? h=')
l=input('Qual é a base? b=')
area=float(h)*float(l)/2
print("A área do triângulo é ",area,".")


Qual é a altura? h=2
Qual é a base? b=1.5
A área do triângulo é  1.5 .

Como já tinhamos referido, comparamos objetos usando operadores relacionais. Já usamos os operadores < e >, que lhe são famíliares da matemática e que interpreta como menor e maior. Por <= e >= o interpretador identifica menor ou iguel e maior ou igual. Já a igualdade de objeto é codificada por ==, por forma a o distinguir do operador de atribuição. Já a relação diferente é codificada por != ou <>.


In [13]:
print(2==2+1)
print(2==2+0)
print(2<=2+1)
print(2<=2+0)
print(2>=2+1)
print(2>=2+0)
print(2!=2+1)
print(2!=2+0)


False
True
True
True
False
True
True
False

Estas relações podem ser aplicados aos mais diferentes objetos. Para um descrição sistemática use a documentação do interpretador, em https://www.python.org/. Aqui vamos restringir as comparações a objetos numéricos. Abrimos apenas execções à igualdade de strings.


In [14]:
print('ab'=='a'+'b')
print('ab'=='a'+'c')
print('ab'!='a'+'b')
print('ab'!='a'+'c')


True
False
False
True

Aqui fica o exemplo mais completo. Note que na célula existem linhas com texto que começam com # (cardinal). O texto que precede # é usualmente designado por comentário, não sendo executado pelo interpretador. Os comentários tentam tornar a implementação mais clara. Têm por principal objectivo tornar o código mais claro.


In [8]:
#
# Programa descobe o número que está referênciado em memória
#

numero = 23 # este é o número que tentamos descobrir

hipotese = int(input('Qual é o número inteiro? '))

if hipotese == numero:
    print('Parabéns, você acertou.') # Novo bloco começa aqui
    print('Tenha um bom dia...') # Novo bloco termina aqui
elif hipotese < numero:
    print('Não, é maior que isso.')  # Outro bloco
    # O bloco pode conter uma ou mais linhas ...
else:
    print('Não, é menor que isso.')

print('Adeus.') 
# Esta última instrução é sempre executada, depois da instrução if 
# ser executada


Qual é o número inteiro? 10
Não, é maior que isso.
Adeus.

Neste programa, é inquirido o utilizador por um número inteiro e é verificado se este é igual a um número escondido.

É obvio que o exemplo é patetico. Já vimos o número e no caso de termos má memória só temos uma tentativa. Na verdade, fora do Jupyter, o utilizador não tem acesso directo ao código, o que torna o exemplo mais "realista". Vaí ter de contínuar a ter de executar o programa várias vezes até resolver o puzzle.

No programa são usadas duas variáveis numero e hipotese. Usa-se a variável numero para referenciar o inteiro a adivinhar, neste caso number = 23. O utilizador tem apenas uma tentativa para adivinhar o número. A hipótese do utilizador é feita através da função input() que acabamos de descrever. Que será referenciada pela variável hipotese. Para isso, a função input, cria uma prompt e espera até que o utilizador escreva uma cadeia de caracteres e tecle ENTER (ou RETURN). A string que o utilizador fornece é usada como valor devolvido pela função. A função int, converte essa string para um inteiro, passado a ser referenciado pela variável hipotese.

Em seguida, é comparado o inteiro referenciado pela variável hipotese com o número referenciado por numero.Se eles forem iguais, imprime-se uma mensagem a felicitar o utilizador. Note que, são utilizados níveis de identação para informar o interpretador de Python que a sequência de instruções pertence a um bloco.

Caso a tentativa do utilizador seja menor que o número referenciado pela variável numero, informa-se que tente na próxima execução um número maior que o número da presente tentativa. Usa-se aqui uma cláusula elif para reduz a quantidade de identações requeridas por estas duas condições. É neste sentido que dizemos que agora o commando if controla agora três blocos de execução: um bloco-if que descreve o que fazer quando os números são iguais; um bloco-elif que diz o que fazer quando o número é maior; e um bloco-else que diz o que fazer caso contrário.

Após ser executada a instrução de um dos blocos (e só um), a execução passa para o próximo bloco de instruções. Neste caso, volta ao bloco principal onde encontra a instrução print('Adeus.'). Após executar esta linha de código, o interpretador termina a execução do código.

Na verdade, não é muito prático ter de executar o programa sempre que se quer fazer uma nova tentativa. Vamos tratar deste problema no próximo Capítulo com a introsução dos ciclos while.

Devemos realçar que as partes elif e else são opcionais. Uma instrução if mínima válida assume a forma:


In [ ]:
if True:
    print('Sim, é verdade')

Não existindo limite ao número de elifs. O bloco controlado por um elif só é executado caso a sua cláusula seja verdadeira e as condições que lhe estão acima sejam falsas.


In [16]:
x = 1

if x == 0:
    print('zero')
elif x==1:
    print('um')
elif x==2:
    print('dois')
else:
    print('Não é nem zero, nem um, nem dois.')


um

Funções definidas em Python

Exercícios de Python

É importante tentar resolver os exercícios seguintes. Alguns podem parecer dificeis, leia com atenção o enunciado, recorde os anteriores e exprimente, exprimente....

Exercício:

Escreva a função abaixo a função:

def mod3(n):
    return n % 3

e execute-a para diferentes argumentos inteiros. Qual a interpretação para o operador % em Python? Qual o comportamento da função para números negativos?


In [ ]:
def mod3(n):
    return n % 3

In [ ]:
mod3(3)
Exercício:

Defina a função incrementaUmaUnidade, que tenha por argumento $x$ e devolva $x+1$. Teste a sua função para $x=3$, $x=5$ e $x=1.5$.


In [ ]:


In [ ]:

Exercício:

Defina a função somaDe1AteN(n), que devolve $1+2+\ldots+n$ usando a fórmula $1+2+\ldots+n=\frac{n(n+1)}{2}$. Teste a função para vários valores de $n$.

Exercício:

Defina a função inverso(x), que devolve $1/x$. Aplique a função para $x=0$. Como é que o Python trata o domínio natural duma função?\label{inverso}

Exercício:

Assumindo definida a função duplica(x), que devolve $2x$, e a função incrementaUmaUnidade(x), dum exercício anterior. Qual é o resultado de duplica(incrementaUmaUnidade(6))? Qual é o resultado de incrementaUmaUnidade(duplica(6))}? Explique esses resultados.

Exercício:

Tente executar incrementaUmaUnidade, dum exercício anterior com argumento '123'. Podemos adicionar um número a uma string em Python?

Exercício:

Tente executar duplica, dum exercício anterior com argumento '123'. Qual o comportamento do operador $\ast$ quando tem por operandos um inteiro e uma string?

Exercício:

No Python, se s é uma string, s[0] identifica o primeiro caracter. Defina e teste uma função que quando aplicada a uma string devolva o seu primeiro caracter.

Exercício:

No Python, [a, b, c, $\ldots$, x] representa uma lista de objectos. Por exemplo [1, 5, 2] representa a lista com três números: 1, 5 e 2. Qual é o comportamento da função dum exercício anterior quando tem por argumento uma lista?

Exercício:

Qual o resultado de aplicar as funções $sum$, $min$ e $max$ (funções pré-definidas) a uma lista de números?

Exercício:

Qual o resultado de executar $min(range(n))$ e $max(range(n))$, quando $n$ é um inteiro positivo?

Exercício:

Reescreva a função somaDe1AteN do exercício anterior por forma a usar funções apresentadas nos exercícios sum e range.

Exercício:

Explique o resultado da execução de 2+-2 e 2++2.


In [ ]:
2+-2

In [ ]:
2++2
Exercício:

Experimente 2+++2. Explique o resultado.


In [ ]:

Exercício:

Experimente 23 e 24. Descreva o comportamento do operador **.


In [ ]:

Exercício:

Experimente "abc" + "def" e 'abc' + 'def'. Descreva o comportamento do operador + quando aplicado a strings.


In [ ]:

Exercício:

O operador * pode ser aplicado a strings? Experimente $3\ast$'12' e explique o resultado.


In [ ]:

Exercício:

Execute 9-82+6 e (5-1)(1+2)**3. Qual a ordem de precedência dos operadores usados nas expressões?


In [ ]:

Exercício:

Definindo a função:

def inverso(x):
    return 1/x

Qual o resultado de executar


In [ ]:
def inverso(x):
        return 1/x

In [ ]:
1 + inverso(2*5)

Como é feita a avaliação quando um dos operadores é uma função? Como é feita a avaliação quando uma função é aplicada a uma expressão?


In [ ]:

Exercício:

A função abaixo devolve o primeiro caracter duma \emph{string}:


In [ ]:
def primeiro(s):
    return s[0]

Adicione à função uma \emph{string} de documentação. Execute:


In [ ]:
primeiro('Bom dia')

e de executar


In [ ]:
primeiro.__doc__
Exercício:

Identifique os erros de sintaxe na definição da função abaixo:


In [ ]:
def codigoErrado(x):
    Return X**2 - 1
Exercício:

No Python, s[-1] identifica a último caracter duma string s (ou o último elemento duma lista s). Escreva uma função em que, de uma string s construa uma nova string contendo dois caracteres: o primeiro e o último caracter em s. Introduza um string de documentação no seu código.


In [ ]:


In [ ]: