O computador é um dispositivo que armazena e manipula (ou processa), automaticamente, dados (ou representações de valores que sejam de nosso interesse). Por isso, toda linguagem de programação oferece meios para expressar valores e para manipulá-los. Nesta lição veremos como podemos usar Python para representar e manipular valores numéricos no computador.
Acho que a melhor forma de começarmos é com um exemplo simples. No replit abaixo, você vê um programa simples que calcula a primeira raíz de uma equação de segundo grau.
Observe, contudo, que o programa não tem nenhum efeito observável ao ser executado: se o executarmos, a máquina vai ler e interpretar o programa, vai armazenar os dados disponíveis e vai calcular os dados desejados por quem escreveu o programa. Mas nenhum desses dados será exibido na tela, porque o programa não instrui o computador explicitamente a imprimir os dados na tela.
Felizmente, o programa é executado em um shell com o qual podemos interagir com a máquina depois do programa ter teminado de executar, mas antes dos dados serem eliminados da memória. Isso nos permitirá explorar os dados e entender melhor o que o programa expressa e como o computador o interpreta. Observe, contudo, que o replit oferece, na verdade, dois shells: um deles estará executando o shell de Python (o que tem o nome console) e o outro executa o shell de Bash (o que tem o nome Shell). O shell Bash permite que você interaja com o sistema operacional e possa executar o programa com python3 main.py. Já o shell Python permite que você interaja com a máquina, imediatamente após a execução do programa, sem que os dados sejam apagados da memória. Os dados só serão apagados, quando o replit for reiniciado (ou recarregado). Você pode ainda executar o programa no botão de play acima do replit. Nesse caso, contudo, você será automaticamente levado para o shell Python.
Execute o programa. Isso fará com que que as variáveis sejam associadas aos valores indicados no programa (as variáveis são a, b, c, delta e x1). Esses valores ficarão disponíveis no shell. Agora, você já pode interagir no shell Python (chamado Console, no replit) e poderá verificar os valores das variáveis. Para isso, basta digitar o nome de cada variável diretamente no shell e dar Enter em seguida. O shell irá imprimir o valor da variável ao final da execução do programa.
Certamente, qualquer estudante que tenha saído há pouco tempo do ensino médio é perfeitamente capaz de entender o programa. A questão é o nível de detalhamento do entendimento de como Python o interpreta. Em particular, nos interessam quatro importantes aspectos de linguagem que este exemplo exploram: literais numéricos, variáveis, expressões e atribuições. Vejamos cada um deles.
Um literal é um pequeno trecho de código (caracteres) que expressa diretamente um valor fixo, específico que a máquina deve colocar na memória. Toda linguagem de programação tem regras léxicas para escrever interpretar literais dos vários tipos de valores suportados pela lingugagem. Em Python, por exemplo, há literais para expressar números inteiros, números com decimais, texto e outros tipos de dados mais avançados que veremos mais adiante.
Importante. As regras léxicas de uma linguagem não são necessariamente as de outras lingguagens.
No programa dado como exemplo, podem ser vistos os seguintes literais, em ordem de ocorrência: 1, -5, 6, 2, 4, 0.5 (identifique-os no texto do programa). Cada um deles expressa um valor que, obviamente, são os números que eles representam em nosso uso cotidiano. A interpretação de Python, contudo, se refere à representação interna que eles receberão. Cada literal encontrado por Python no programa será convertido para uma representação binária interna própria da linguagem, de acordo com seu tipo e valor. Isso quer dizer que o literal 0.5, por exemplo, não será reconhecido como um inteiro e terá uma representação interna bastante diferente dos demais literais acima.
Se você estiver curioso, uma representação interna em bits de
0.5é00111111000000000000000000000000; já a de0.4pode ser00111110110011001100110011001101. Parece não fazer muito sentido, mas mais adiante veremos como isso se dá.
Léxico A regra léxica que determina como são expressos e interpretados literais de inteiros é bastante simples. Uma sequência ininterrupta de digitos iniciada por qualquer digito (caracteres entre 0123456789) que pode ou não ser iniciada por um sinal + ou -. Assim, são literais válidos 120, 52, 10, +22, -15, -1 (mas atenção, os literais de inteiros nunca devem ser iniciados por um 0… você pode tentar usar no programa acima, definindo a = 07 e poderá ver que o programa não funcionará, provocando um erro… mais adiante veremos o motivo pra isso.)
Observe que um literal no programa exemplo não se encaixa bem na regra léxica dada: 0.5, dado que não se trata de uma sequência ininterrupta de dígitos. A menos que separássemos as partes do literal de dois literais independentes 0 e 5. Felizmente, Python reconhece o ponto decimal como um caractere válido quando usado entre duas sequências ininterruptas de digitos. Nesse caso, ele considera que se trata do literal de um número de ponto flutuante.
Números de ponto flutuante são uma forma de representação aproximada do que chamamos no quotidiano de números com decimais (com um número limitado de casas após a vírgula). Infelizmente, não permitem representar reais de modo geral. Trata-se de uma forma mais adequada para a representação e manipulação interna para esse tipo de número.
Importante Ao interpretar os programas, Python produzirá e armazenará valores e representações internas do tipo inteiro para os literais inteiros e valores e representações internas do tipo ponto flutuante, para literais com ponto decimal. Isso implica que os literais 1 e 1.0 que, conceitualmente, parecem nos representar a mesma coisa são interpretados e representadas de formas bastante diferentes por Python (e pela maioria das demas linguagens de programação). Se por ora isso parece irrelevante para você, não se preocupe, mais adiante voltaremos ao assunto e você verá a relevância desse assunto. Por ora, apenas mantenha em mente que 1 e 1.0 são dados diferentes e que isso pode vir a produzir resultados diferentes e, aparentemente, inconsistentes em relação ao que você espera.
Um outro aspecto importante que nosso programa exemplo permite abordar é o uso de variáveis e de atribuições. Uma variável é um nome ou um identificador que associamos a um valor armazenado pelo programa na memória do computador. Em nosso exemplo definimos 5 variáveis: a, b, c, delta e x1.
Sintaxe Uma atribuição é o comando Python que vincula um valor a um nome (variável). O comando tem a seguinte sintaxe:
<nome> = <expressao que calcula o valor>
Obviamente, acima, você deve substituir <nome> pelo nome desejado e <expressao que calcula o valor> pela expressão desejada e que efetivamente calcula o valor a ser associado ao <nome>. O sinal de igualdade = é obrigatório e é o que faz Python identificar o trecho de código com uma atribuição. Cada linguagem tem suas regras para a formação de nomes de variáveis. Python exige que os nomes sejam iniciados por letras ou _ e que os demais caracteres sejam letras, digitos ou o próprio _. Maiúsculas e/ou minúsculas são aceitáveis e são diferenciadas. São variáveis válidas: abc, Abc, abc1001, idade, _idade e idade_pai. Não são variáveis válidas idade pai (espaços não podem ser usados), 1a (não podem iniciar com números), "a" (aspas e outros caracteres de pontuação não podem ser usados como parte dos nomes de variáveis).
Semântica. Quando Python executar um programa e encontrar um comando de atribuição, ele o interpreta da seguinte forma. A expressão do lado direito da atribuição será avaliada e produzirá um valor final. O local de memória em que o valor produzido será armazenado será vinculado ao nome do lado esquerdo da atribuição. A partir desse ponto em diante, a variável poderá ser usada em outras expressões. E quando for usada e precisar ser interpretada, a variável será resolvida (tipicamente, dizemos dereferenciada), resultando no valor ao qual está associada.
Isso explica por que em nosso exemplo 1, acima, os valores de a, b e c puderam ser definidos nas primeiras linhas do programa e depois puderam ser usadas para calcular delta e x1.
A ordem importa Observe que a ordem em que as atribuições são feitas importa muito. Um programa não é como a matemática, em que poderíamos ter escrito as linhas que definem os valores de a, b e c depois das linhas que definem delta e x1, por exemplo.
Exercício No repl abaixo (que é o mesmo exemplo 1 acima) coloque as definições de a, b e c depois de definir delta, por exemplo. E observe o que acontecerá quando você executar o programa.
NameErrorSe você fez o exemplo acima, verá que o programa não executou corretamente. Dizemos que o programa quebrou, dado que sua execução foi interrompida por Python, logo na primeira linha. O erro apresentado foi NameError.
Esse é um dos erros mais comuns que o estudante enfrenta quando começa a programar. Trata-se, como você pode ver pelo exemplo, de um programa em que uma variável (ou nome, como prefere Python) está sendo usada antes que tenha sido definida. Observe que o erro que aparece na última linha indica também qual variável causou o erro:
Traceback (most recent call last):
File "python", line 1, in <module>
NameError: name 'b' is not defined
No exemplo acima, a variável b é a acusada. Para corrigir o erro, cabe a você identificar a variável e verificar se alguma atribuição para aquele nome foi feita antes da linha em que o erro ocorre.
Para entender o programa falta um elemento que deixamos para o final: expressões. O motivo para isso é que você certamente já está familiarizado com expressões matemáticas e este programa só usa expressões com operadores com os quais você certamente está familiarizado. Mas vale a pena olharmos para o assunto em maior detalhe.
Sintaxe Uma expressão é um trecho de código Python que expressa uma computação. Aqui, entenda computação no sentido mais estrito. Computar significa calcular, produzir novos valores a partir de outros valores já existentes. É importante saber separar o conceito de expressão enquanto texto ou código e o da computação implicada por ela. A expressão é uma sentença em código que expressa como uma computação deve ser feita a partir de valores já existentes e operações pré-determinadas. A sentença em si (o código) consiste, portanto, em literais, variáveis e operadores (símbolos que representam as operações). Os operadores usados em nosso exemplo foram: **, -, *, e + que, por sua vez, representam operações algébricas conhecidas nossas. Vale ressaltar que o operador * é usado para multiplicação e ** para exponenciação.
Semântica Quando um programa está sendo executado e Python se depara com uma expressão, dizemos que ele a avalia. O resultado da avaliação da expressão é o valor final calculado para toda a expressão. É esse resultado que é considerado o valor da expressão em si, quando for avaliada.
A avaliação de uma expressão é a computação propriamente dita. Por isso, as regras de avaliação de expressões de uma linguagem estão entre os conceitos mais importantes a se aprender para programar bem em qualquer linguagem. Infelizmente, é uma das coisas mais negligenciadas por programadores novatos, por se basearem em uma visão intuitiva, apoiada pela experiência com expressões matemática e/ou de outras linguagens.
Expressões não são necessariamente compostas apenas por dois valores e um operador. Tipicamente, precisamos escrever expressões complexas. Isso muitas vezes envolve a escrita de expressões que contêm (sub-)expressões dentro delas. Algumas incluindo o uso de parênteses. Daí a necessidade de entender como Python avalia expressões.
Como a expressão 1 + 2 * 3 deve ser avaliada? Resultará em 9 ou em 7? Isso depende da ordem em que avaliarmos as sub-expressões. Ou melhor, da ordem em que executarmos as operações de soma e de multiplicação. Se fizermos primeiro a soma, teríamos a seguinte derivação.
1 + 2 * 3 =>
3 * 3 =>
9
Se fizermos primeiro a multiplicação, teremos:
1 + 2 * 3 =>
1 + 6 =>
7
A forma de resolver esse problema e dar à expressão uma única possível interpretação é definir o que chamamos de ordem de precedência dos operadores. A ordem de precedência determina em que ordem dois operadores que poderiam ser avaliados ao mesmo tempo devem ser necessariamente avaliados. Felizmente, a ordem de precedência em Python é a mesma que usamos na matemática que aprendemos. Neste caso, * tem maior precedência que o + e o resultado será 7 e não 9.
Para os operadores algébricos, a ordem de precedência é dada abaixo (da mais alta à mais baixa):
**
* /
+ -
Parênteses Parênteses podem ser usados em expressões para forçar a ordem de operações que não devam seguir a ordem de precedência estabelecida. Por exemplo, a expressão (1 + 2) * 3 produz efetivamente 9 porque os parênteses serão respeitados por Python. Ele fará primeiro a resolução da sub-expressão 1 + 2 e com o resultado dessa operação fará a operação de multiplicação por 3.
Exercício Releia o programa exemplo. Perceba que há um erro nele. O programa não calcula corretamente o valor de x1. Relembre a fórmula de Bhaskara para o cálculo das raízes e veja que o código em Python não implementa corretamente a fórmula. Dica: o problema está justamente na ordem em que as operações são feitas. Dica 2: esse erro é extremamente comum e vale a pena ficar atento para situações parecidas a essa que você possa vir a enfrentar. Assim, tente entender o que causou o erro.