domingo, 6 de novembro de 2011

Erros Vivos (2)

Consideremos a listagem seguinte:


>>> def toto(n):
... print n + 1
...
>>> def titi(n):
... return n + 1
...
>>> toto(4)
5
>>> titi(4)
5
>>> print toto(4)
?
>>> print titi(4)
?
>>> toto(4) + 1
?
>>> titi(4) + 1
?


O que vai parecer no lugar dos pontos de interrogação? Esta era uma das perguntas de um dos testes. O que estava em jogo essencialmente era saber em que medida se compreendia a diferença entre existir ou não a instrução de return e a diferença para um print. Pedia-se também a justificação e isso era muito importante para a aceitar a resposta.

Vejamos dois exemplos de resposta. São apenas indicativos dos problemas evidenciados por um número muito grande de alunos.

Resposta 1

Na linha 12 (primeiro ponto de interrogação) o resultado será 5 e na linha 14 (segundo ponto de interrogação) também será 5, a diferença dos dois é que na função toto, os valores serão retornados por um print e na função titi serão retornadas por um return. Na linha 16 (terceiro ponto de interrogação) irá dar um ewrro, pois não se pode somar +1 a uma função e na linha 18 (quarto ponto de interrogação) como a função retorna um valor pela instrução return o resultado será 6.

Resposta 2

Linha 12:
aparecerá
>>> 5
>>> 5
Porque há duas instruções de print, uma dentro da função e outra de fora dela.

Linha 14:
Aparecerá
>>> 5
Porque o resultado apenas é imprimido uma vez.

Linha 16:
Aparecerá
>>> 6
Porque mais uma vez a impressão é feita apenas uma vez.

Linha 18:
Aparecerá
>> None
Porque o resultado não foi mandado imprimir.

Vamos então ver os problemas. Comecemos por um facto: trata-se de uma sessão no interpretador. Assim sendo, todas as expressões que colocarmos estão sujeitas a um processo que se desdobra em três fases: leitura da expressão, cálculo do seu valor e impressão do resultado. Trata-se do ciclo READ-EVAL-PRINT já referido nas aulas. Repito: aplica-se a todas as expressões. E o que é uma expressão? Pode ser um objecto (com valor), um nome associado a um objecto, ou uma função/método aplicada/o aos seus argumentos. Vejamos um exemplo.


>>> a = 5
>>> 7
7
>>> a
5
>>> a + 5
10
>>> import math
>>> math.sin(3)
0.1411200080598672
>>> math.sin(a)
-0.9589242746631385
>>> math.sin(math.sin(a))
-0.8185741444617193
>>>

Quando damos um objecto (7) o respectivo valor é ecoado; quando introduzimos o nome a é calculado o valor do objecto associado e este é ecoado. Quando introduzimos uma expressão mais complexa ela é avaliada e o valor do objecto resultado é ecoado. Podemos ter expressões mais bizarras como a que envolve calcular o seno do seno de um número.

O leitor atento perguntará: mas porque é que no primeiro caso da listagem (a = 5), nada é ecoado? A razão é simples: porque não é uma expressão mas antes uma instrução. É por essa mesma razão que quando definimos no interpretador uma função, nada é ecoado. Estamos apenas a definir usando a instrução composta def. Mas quando usamos uma definição, ela tem a natureza de uma expressão e por isso o valor que devolve vai ser ecoado!

>>> def dobro(n): # <-- Definição
... return 2*n
...
>>> dobro(3) # <-- Uso
6
>>> dobro(dobro(2)) # <-- Uso duas vezes
8
>>> dobro(4) + 1 <-- Uso
9
>>>

Mas como sabemos qual é o valor que a chamada (o uso) de uma função devolve? Simples! É o valor da expressão associada ao primeiro return encontrado durante a execução da função. Mas, e se a função não tiver nenhum return ??? Já sabemos a resposta: devolve na mesma um objecto chamado None! Mas devemos ter em atenção que None denota a ausência de valor!!

>>> None
>>> print None
None
>>>

Daí que None não seja ecoado pelo interpretador mas seja impresso por print.

Percebido isto, já estamos em condições de responder com correcção à pergunta. Ou talvez não... Ora vamos lá a ver outra situação.


>>> a = 5
>>> a
5
>>> print a
5
>>>

Mas última situação retratada, não deveria aparecer duas vezes o valor 5? Uma pelo print e a outra porque o interpretador depois de ler, avaliar imprime o resultado? Não, não devia, porque print a não é uma expressão! Logo só há a impressão devido ao print. Então e se a instrução de print aparecer no interior de uma definição. O que é que acontece? Olhemos para a primeira situação da pergunta:

>>> def toto(n):
... print n + 1
...
>>> toto(4)
5
>>>

Expliquemos de novo: definir a função toto não faz ecoar nada. Mas quando usamos a função, chamando com o argumento 4 (toto(4)) porque aparece 5? Apenas 5! Afinal toto(4) é uma expressão. Sim, é verdade, mas na ausência de um return o objecto devolvido ao interpretador é None que, como dissemos denota a ausência de valor. Então o 5 que aparece resulta da instrução de print dentro da definição. UIff! Agora é que está tudo certo.

Regressemos então a sessão da pergunta.

>>> def toto(n):
... print n + 1
...
>>> def titi(n):
... return n + 1
...
>>> toto(4)
5
>>> titi(4)
5
>>> print toto(4)
?
>>> print titi(4)
?
>>> toto(4) + 1
?
>>> titi(4) + 1
?

As duas definições não ecoam nada. As duas chamadas seguintes fazem aparecer 5. Mas a primeira ocorrência de 5 é devida ao print , enquanto que a segunda resulta do interpretador imprimir o objecto devolvido pelo return da definição. Agora o primeiro ponto de interrogação. Vai aparecer:


>>> print toto(4)
5
None


O 5 resulta da instrução de print, enquanto que o None é ecoado pelo interpretador e resulta do facto de não havendo return esse é o objecto devolvido e que print tem que imprimir.

Terceiro ponto de interrogação:

>>> print titi(4)
5

Aqui é fácil: a função devolve 5 (devido ao return), que é ecoado pelo interpretador. Já sabemos que o print nem era preciso.

Terceiro ponto de interrogação.

>>> toto(4) + 1
5
Traceback (most recent call last):
File "", line 1, in
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
>>>

O 5 vem da instrução de print. O erro deriva do facto de que, na ausência de return, o valor devolvido é None, e este objecto não pode ser somado com um inteiro. Logo dá erro.

Quarto ponto de interrogação.

>>> titi(4) + 1
6

Aqui tudo é mais “normal”: a função devolve 5, este valor é somado a 1, e este é o valor da expressão que o interpretador ecoa.

That’s it!

Sem comentários:

Enviar um comentário