HomePage RecentChanges

Lesson05

Ainda que as primitivas e operações pré-definidas estudadas até agora nos permitam já elaborar programas complexos, em programação é útil podermos agrupar funcionalidades e dividir os nossos programas em módulos. Isto permitirá em particular reutilizar código em diferentes programas. Este aspecto da programação está em particular ligado aos conceitos de função, procedimento e/ou subprograma. Mais tarde veremos que podemos agrupar as funções em módulos ou bibliotecas. Quer as funções quer os módulos são nomeados, após o que podemos referirmos-nos aos mesmos e utilizá-los nos nosso programas pelo nome.

As funções em Python, e em programação em geral, são idênticas ao que conhecemos da Matemática. Uma função tem um determinado número de argumentos cujo conjunto de valores possíveis se designa por domínio ou conjunto de partida. Uma função retorna também em geral um valor, cujo conjunto de valores possíveis se designa por contradomínio ou conjunto de chegada. Note-se que em programação uma função pode não retornar valor algum, podendo produzir antes efeitos (por exemplo enviar dados para a rede).

Em programação as funções são em geral definidas por compreensão ou abstracção por oposição a definição por extensão ou enumeração, em particular porque o domínio é em geral infinito. A definição de funções por compreensão implica a descrição do processo ou algoritmo para determinar o valor de retorno a partir dos valores dados como argumentos, em geral através de uma expressão designatória em que ocorrem variáveis livres, i.e., parâmetros, que podem ser substituídas por valores, produzindo uma designação ou expressão concreta, e que corresponde ao que comummente designamos por aplicação de função ou chamada de função.

Portanto, de forma idêntica ao que conhecemos da Matemática, a utilização de funções em programação compreende a definição da função (nome, argumentos e algoritmo) e aplicação de função (execução do algoritmo sobre valores passados como argumentos). Vejamos como definir e aplicar funções em Python.

Definição de funções

Em Python podemos definir funções indicando o nome da mesma, os seus argumentos e a sequência de instruções a executar pela função, i.e., o corpo da função. Esta sequência de instruções ou corpo corresponde ao algoritmo que se pretende executar quando utilizamos a função. Em notação BNF,

<definição de função> ::=
    def <nome> (<parâmetros formais>): NEWLINE
    INDENT <corpo> DEDENT

<parâmetros formais> ::= <nada> | <nomes>

<nomes> ::= <nome> | <nome>, <nomes>

<nada> ::= 

<corpo> ::= <definição de função>* <instruções em função>

<instruções em função> ::=
    <instrução em função> NEWLINE |
    <instrução em função> NEWLINE <instruções em função>

<instrução em função> :: <instrução> | <expressão> | <instrução return>

<instrução return> ::= return | return <expressão>

Vejamos o seguinte exemplo:

def soma(n):
    iter = 1
    soma = 0
    while iter <= n:
        soma = soma + iter
        iter = iter + 1
    return soma

Aplicação de funções

As funções uma vez definidas podem ser utilizadas tal como as funções pré-definidas do Python. Para utilizarmos umas função basta invocar a mesma utilizando o seu nome e passando parâmetros concretos, i.e., expressões em número igual aos parâmetros formais indicados na definição da função. Em notação BNF,

<aplicação de função> ::= <nome>(<parâmetros concretos>)

<parâmetros concretos> ::= <nada> | <expressões>

<expressões> ::=
    <expressão> |
    <expressão>, <expressões>

Consideremos o seguinte exemplo me que utilizamos a função soma definida acima:

>>> soma(100)
5050
>>> soma(50,75
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: soma() takes 1 positional argument but 2 were given
>>> soma()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: soma() missing 1 required positional argument: 'n'
>>>

Neste exemplo podemos verificar que o Python retorna erro se o número de parâmetros concretos for diferente do número de parâmetros formais.

Anteriormente discutimos a noção de ambiente, i.e., um mapa que associa nomes a entidades durante a execução dos programas. Na realidade o ambiente que considerámos até agora designa-se de ambiente global. Quando invocamos funções são criados ambientes locais que, para além das associações existentes no ambiente global, incluem também as associações locais à função, nomeadamente as associações entre os nomes dos parâmetros formais e os respectivos parâmetros concretos, e as associações que sejam definidas no âmbito da função, como as atribuições a variáveis locais à função. No exemplo anterior, os nomes iter e soma apenas existem no ambiente local à função soma (e mesmo que existissem no ambiente global, no ambiente local e apenas no ambiente local seriam substituídos pelas novas associações). Quando existe uma associação para um dado nome diz-se que é um nome ligado, caso contrário diz-se não ligado.

Notar que os ambientes têm um comportamento hierárquico. Quando definimos funções dentro de funções, estas herdam as associações do ambiente envolvente, podendo substituir as associações herdadas por outras no âmbito local.

Quando uma função é invocada em Python ocorrem os seguintes passos:

  1. Os parâmetros concretos são avaliados (por uma ordem arbitrária).
  2. Os parâmetros formais da função são associados aos valores dos parâmetros concretos no ambiente local, pela mesma ordem respectivamente.
  3. As instruções do corpo da função são executadas nesse ambiente local, que apenas existe enquanto a função não terminar, sendo apagado após o término da mesma.

O resultado de uma função é passado para o ambiente envolvente da função através da execução da instrução return a qual retorna o valor (se existir uma expressão a avaliar, caso contrário não retorna nada) e termina a função. Se não existir uma instrução return no corpo da função, esta termina sem retornar qualquer valor.

Abstracção procedimental

A abstracção procedimental consiste em abstrair de como as funções operam e focar no que as funções realizam, i.e., separar o "como" do "o que". Por outro lado consiste em dar um nome à sequência de operações que serve para produzir um determinado resultado e/ou efeito, sendo que podemos utilizar esse nome para invocar a função.

Exemplo de duas funções que fazem o mesmo mas de formas diferentes:

def soma(n):
    iter = 1
    soma = 0
    while iter <= n:
        soma = soma + iter
        iter = iter + 1
    return soma
def soma(n):
    if n < 1:
        return 0
    return n*(n+1)//2

O resultado produzido por ambas as funções soma é o mesmo, mas fazem-no de forma diferente.

Visualização da execução de programas

Para visualizar ou analisar a execução de programas passo a passo podemos utilizar um debugger como o pdb, também acessível na maior parte dos IDEs como o Wing, ou podemos utilizar ferramentas como http://pythontutor.com/visualize.html.