A função é um recurso muito importante para a boa estruturação de um código. Quando bem utilizada, ela torna o código fonte mais claro e menos repetitivo. Por isso, nesse post vou abordar os princípios básicos das funções em Python. De quebra, você vai aprender um pouco sobre alguns conceitos relacionados a funções, como o de passagem por referência e por valor, e o de mutabilidade e imutabilidade de objetos em Python.
O que é uma função em Python?
Uma função nada mais é que um bloco de código que executa um determinado procedimento. Mas, diferente de blocos de código que se encontram dentro de condicionais (if / elif / else) ou de laços de repetição (for / while), o procedimento definido numa função pode ser executado a qualquer momento, e quantas vezes for necessário, bastando para isso que a função seja “chamada”. A única restrição nesse sentido, é que a função deve ser definida antes de ser chamada, isto é, você deve escrever o código da função antes de chamá-la pela primeira vez.
Definindo uma função em Python
Para definir uma função em Python, utilizamos a seguinte estrutura:
def nome_da_funcao(parâmetros):
conteúdo da função
A palavra-chave def é o que indica que estamos declarando uma função. Os parâmetros serão explicados daqui a pouco com mais detalhes. Por enquanto, basta saber que eles dizem respeito aos valores que devem/podem ser passados para a função para que ela os utilize na realização de sua “tarefa”. Assim como fazemos quando criamos outros blocos de código, como um bloco if ou for, também devemos indicar o início do bloco de código com dois pontos (:) e um novo nível de indentação.
Para ilustrar, vamos criar uma função bem simples, que não recebe valores:
def saudacao(): print("Olá! Tudo bem?")
Como a função não recebe valores, então sua lista de parâmetros é vazia.
Agora que definimos a função, podemos chamá-la a qualquer momento através da chamada saudacao().
Você pode fazer um teste no próprio interpretador interativo do Python:
>>> def saudacao(): ... print("Olá! Tudo bem?") ... >>> saudacao() Olá! Tudo bem? >>> saudacao() Olá! Tudo bem? >>> print("bla" * 4) blablablabla >>> saudacao() Olá! Tudo bem?
Como você pode observar, depois de definida, a função pode ser chamada a qualquer momento.
Parâmetros e argumentos de funções
Como acabei de dizer, uma função pode receber valores para utilizar internamente. Os valores que a função deve receber no momento em que é chamada são armazenados em variáveis que são definidas durante a criação da função. Esses nomes de variáveis são chamados de parâmetros da função. E os valores efetivamente enviados para uma função e armazenados nessas variáveis no momento em que a função é chamada são chamados de argumentos. Essa é uma diferença sutil, e que confunde muita gente.
Utilizando parâmetros, podemos deixar nossa função um pouco mais “versátil”. Vejamos:
def saudacao(nome): print("Olá,", nome, ", tudo bem?")
A nova versão da nossa função saudacao() possui um parâmetro, chamado nome. Quando a função é chamada, ela recebe um valor como argumento, que é armazenado na variável nome e utilizado no print (observe que print() também é uma função, definida pela própria linguagem!). Podemos fazer mais alguns testes no interpretador:
>>> def saudacao(nome): ... print("Olá,", nome, ", tudo bem?") ... >>> saudacao("amador") Olá, amador , tudo bem? >>> n = "programador" >>> saudacao(n) Olá, programador , tudo bem? >>>
Você pode definir quantos parâmetros forem necessários. Para isso, basta enumerá-los separados por vírgula dentro dos parênteses que sucedem o nome da função em sua definição. Por exemplo:
def soma_tres(a, b, c): print(a+b+c)
Observe também que assim como ocorre com outros blocos de código, os argumentos e outras variáveis declaradas dentro de uma função têm escopo local, isto é, elas podem ser referenciadas apenas dentro da função, e depois que a função termina, essas variáveis não podem mais ser acessadas e seus valores são perdidos.
Parâmetros nomeados em Python
A linguagem Python também permite que passemos valores para as funções designando para qual parâmetro enviaremos determinado valor de forma explícita. Vejamos um exemplo:
def ir_para_lugar(quem, onde): print(quem, "vai para", onde)
Agora, testando a função no interpretador:
>>> ir_para_lugar('Programador', 'a Google') Programador vai para a Google >>> ir_para_lugar(onde = 'a Google', quem = 'Programador') Programador vai para a Google
Na segunda chamada da função, invertemos a ordem dos argumentos, mas obtivemos o mesmo resultado porque definimos de forma explicita para que parâmetro deveria ir cada valor.
Também é possível estabelecer valores padrão para os parâmetros de suas funções. Assim, se um valor for passado como argumento, ele é utilizado pela função, caso contrário, a função utilizará um valor padrão. Por exemplo:
>>> def ir_para_lugar(quem = "Fulano", onde = "onde Judas perdeu as botas"): ... print(quem, "vai para", onde) ... >>> ir_para_lugar() Fulano vai para onde Judas perdeu as botas >>> ir_para_lugar("Beltrano") Beltrano vai para onde Judas perdeu as botas >>> ir_para_lugar(onde = "Portugal") Fulano vai para Portugal >>> ir_para_lugar(onde = "Portugal", quem = "Manoel") Manoel vai para Portugal
Nesse exemplo, tanto o parâmetro quem quanto o parâmetro onde, são opcionais. Caso a função seja chamada sem argumentos, será impressa na tela a mensagem “Fulano vai para onde Judas perdeu as botas”.
Os argumentos podem ser passados de forma posicional ou nomeada. Mas a partir do primeiro argumento passado por nome, todos os argumentos seguintes devem ser passados por nome também, caso contrário, temos um erro de sintaxe:
>>> ir_para_lugar(onde = "Portugal", "Manoel") File "<stdin>", line 1 SyntaxError: positional argument follows keyword argument
Devolvendo valores de funções com return
Além de receber valores, uma função também pode devolver valores para a expressão que a chamou utilizando a palavra-chave return.
Vamos ilustrar a utilização do return com um exemplo mais próximo do conceito de função que conhecemos da matemática. Vamos implementar uma função que retorna o quadrado de um certo número.
def quadrado(n): return n * n
Essa simples função recebe um certo valor n como argumento e retorna o resultado da multiplicação desse número por ele mesmo:
>>> def quadrado(n): ... return n * n ... >>> quadrado(3) 9 >>> quadrado(3) * 2 18 >>>
Como é possível observar pelo exemplo acima, uma chamada de função presente em uma expressão é substituída pelo seu valor de retorno para que a expressão seja resolvida.
Tenha em mente que o fluxo de execução do programa deixa o bloco da função e retorna para o bloco que a chamou assim que encontra um return. Ou seja, qualquer instrução colocada após um return não é executada.
Exemplo no interpretador:
>>> def quadrado(n): ... return n * n ... print("Você jamais lerá essa frase") ... >>> quadrado(2) 4
Por fim, você pode definir o ponto do código em que a função deve retornar, mesmo sem especificar um valor de retorno, bastando para isso utilizar return sem nenhum valor em seguida:
>>> def roda_ate_cansar(): ... while True: ... r = input("Deseja continuar (S/N)?") ... if r.upper() == 'N': ... return ... >>> roda_ate_cansar() Deseja continuar (S/N)?S Deseja continuar (S/N)?S Deseja continuar (S/N)?S Deseja continuar (S/N)?S Deseja continuar (S/N)?N >>>
O método upper() converte todas as letras da entrada do usuário para maiúsculas. Assim, não precisamos testar se r == ‘N’ ou r==’n’. Essa função irá repetir o loop até o usuário digitar a letra ‘N’. Quando isso ocorrer, o fluxo de execução do programa retornará para o local do código imediatamente depois do local em que a função foi chamada.
Uma função que retorna valores, isto é, que produz resultados, é também denominada uma função frutífera, enquanto uma função que não retorna valores é chamada de função infrutífera.
Passagem por valor vs. passagem por referência
Outro aspecto importante no que diz respeito às funções em Python é a estratégia utilizada pela linguagem para a passagem de argumentos para as funções.
No universo da computação, existem duas formas mais difundidas de se passar argumentos para funções: por valor, ou por referência.
Resumidamente, na estratégia de passar por valor (pass by value), a expressão utilizada como argumento para uma função é resolvida para um valor que é então copiado para a variável local da função. Apesar de não ser uma linguagem que utiliza a passagem por valor, estritamente falando, podemos observar um efeito semelhante a esse no Python executando o seguinte trecho de código no interpretador:
>>> def soma_um(n): ... n += 1 ... print(n) ... >>> a = 3 >>> soma_um(a) 4 >>> a 3
Perceba que o valor da variável n é alterado, mas o da variável a continua intacto.
Já na estratégia de passagem por referência, o valor da expressão utilizada como argumento não é copiado para uma variável local. Ao invés disso, é passada para a função uma referência ao mesmo objeto utilizado como argumento. Também é possível observar esse comportamento de passagem por referência (pass by reference) no Python. Vejamos o próximo exemplo:
>>> def insere_num(lista, num): ... lista.append(num) ... >>> a = [1, 2, 3, 4] >>> insere_num(a, 5) >>> a [1, 2, 3, 4, 5]
Observe que dessa vez as modificações feitas no objeto dentro da função se refletem no objeto original, fora da função.
Acontece que no Python ocorre a chamada passagem por referência ao objeto (pass by object reference). Em Python, quando atribuímos um valor a uma variável, estamos apenas ligando um nome (o nome da variável) a um objeto (o valor da variável), e quando utilizamos uma variável como argumento para uma função, estamos enviando para a função uma referência ao objeto que essa variável “nomeia”. A partir daí, a variável local da função e a variável utilizada como argumento funcionam como nomes diferentes para o mesmo objeto, de modo semelhante ao funcionamento da passagem por referência. Mas, como vimos no exemplo da função soma_um(), é possível alterar uma variável local sem que isso altere o objeto original! Por que isso acontece?
Mutabilidade e imutabilidade dos objetos em Python
Isso está ligado a um outro conceito muito importante no Python. A mutabilidade e imutabilidade dos objetos. Alguns objetos em Python, como strings, inteiros e tuplas, são objetos imutáveis. Isto é, uma vez definidos, eles não podem ser modificados. Quando executamos instruções como:
a = 3 a = 7
Não estamos modificando o valor de um objeto inteiro de “3” para “7”. Estamos, na verdade, criando um novo objeto inteiro com o valor “7” e mudando o objeto ao qual o nome “a” é ligado. É o que ocorre no nosso exemplo do soma_um(). Quando resolvemos a expressão n += 1, um novo objeto é criado e n passa a designar esse novo objeto, enquanto a variável a continua designando o antigo objeto.
Por outro lado, alguns objetos em Python são mutáveis, como listas, dicionários, conjuntos e as instâncias de classes definidas pelo usuário. Vejamos um exemplo:
a = [1, 2, 3] a += [4]
Nesse caso, a segunda instrução não cria uma lista nova com o valor [1, 2, 3, 4]. Ao invés disso, o mesmo objeto é modificado e passa ter esse novo valor. Por isso a função insere_num() modifica a lista original.
Quando você quiser simular o efeito de uma passagem por valor em Python com objetos mutáveis, é necessário utilizar certas estratégias. No caso de listas e dicionários, por exemplo, você pode utilizar o método copy(), como no exemplo abaixo:
>>> dict1 = {"a": 1, "b": 2} >>> dict2 = dict1.copy() >>> dict2["c"] = 3 >>> dict1 {'a': 1, 'b': 2} >>> dict2 {'a': 1, 'b': 2, 'c': 3}
Tenha sempre em mente a estratégia de passagem de argumentos utilizada pelo Python, pois partir da suposição de que você sempre estará trabalhando com uma cópia quando estiver lidando com uma variável local de uma função fatalmente levará a erros!
Isso dá ensejo à apresentação de alguns conceitos novos sobre funções.
Funções com efeitos colaterais e funções puras
Quando uma função é capaz de alterar valores de variáveis que não são locais, como as variáveis mutáveis passadas como argumento, ou variáveis globais às quais ela tem acesso, dizemos que ela é uma função com efeitos colaterais.
Por outro lado, se uma função não interfere em variáveis de fora de seu escopo e também não depende dos valores de variáveis globais ela é denominada uma função pura. O resultado de uma função pura (seu valor de retorno), depende unicamente dos valores passados a ela como argumento, o que significa que a função sempre retornará o mesmo valor para os mesmos argumentos. Isso é chamado de comportamento determinístico.
Esses são alguns dos conceitos fundamentais para se trabalhar com funções em Python. Agora que você já está dominando essa parte, pode estudar outros tópicos relacionados a funções, como argumentos variáveis, funções lambda e recursão. Eu ainda pretendo falar sobre cada um desses assuntos aqui no blog, por isso não deixe de acompanhá-lo, e se tiver qualquer dúvida, deixa seu comentário! 😉
3 thoughts on “Funções em Python: o que são e como utilizar”