Funções em Python - #16
Tags: beginners, programming, python, tutorial
Published: 2022-07-12 23:19:29

{% youtube https://youtu.be/R1Z69FTR3rE %}
{% youtube https://youtu.be/pPXZhL0NGrk %}

## Introdução

Uma **função** é um miniprograma dentro de um programa. É uma estrutura da linguagem que possibilita uma melhor organização de seu programa e facilita a resolução de problemas mais complexos, através delas podemos resolver partes específicas do problema.

Outra vantagem do uso de funções é a possiblidade de reutilização do código, podemos por exemplo, criar uma biblioteca de funções e utilizá-las em nossos programas, sem a necessidade de reescrever a mesma funcionalidade.

Neste capítulo iremos aprender a usar outras funções disponíveis na linguagem como também a criar as nossas próprias funções.

## Função **_bool()_**

A função **_bool()_** retorna **_True_** ou **_False_** ao receber um parâmetro.

Se o valor é numérico, ele retornará **_False_** se o parâmetro é igual a zero.

Para **_strings_**, listas e dicionários vazios, o retorno também será **_False_**. Como também para o valor **_None_**.

```python
#função bool() em ação
>>> bool(-2)
True
>>> bool(0)
False
>>> bool(0)
False
>>> bool(-2)
True
>>> bool(0.00)
False
>>> bool(3.14)
True
>>> bool(None)
False
>>> bool('casa')
True
>>> bool('')
False
>>> bool(' ')
True
>>> bool([])
False
>>> bool([1])
True
```

## Função **_abs()_**

A função **_abs( )_** retorna o valor absoluto de um número. O valor sem sinal.

```python
#função abs() em ação
>>> abs(-20)
20
>>> abs(20)
20
>>> abs(-5.433)
5.433
>>> abs(0)
0
```

## Usando módulos

As funções em _Python_ são organizadas em **_módulos_**. Cada módulo possui um conjunto de funções relacionadas, que podem ser utilizadas em nossos programas.

Antes de utilizarmos funções de um módulo, precisamos importar o módulo através da instrução **_import_**.

### Instrução **_import_**

Por exemplo, se quisermos gerar 3 números inteiros de forma randômica ou aleatória, entre 1 e 11 com a função **_randint()_**:

```python
#Algoritmo - gerando 3 números inteiros aleatórios
import random
for i in range(3):
    print(random.randint(1, 10))
```

Na linha 01 temos que **_importar_** o módulo **_random_** que contém a função **_randint( )_**.  

Na linha 03 fazemos a chamada do método, utilizando o nome do módulo e o nome da função, concatenados com um ponto (“.”) .

```
#Execução - gerando 3 números inteiros aleatórios 
10
7
4
```

### Instrução **_from_** - **_import_**

Uma alternativa na sintaxe **_import_** é utilizar a palavra reservada **_from_** e o asterisco (“*”):

```python
#Algoritmo - gerando 3 números inteiros aleatórios versão 2
from random import *
for i in range(3):
    print(randint(1, 10))
```

Com a sintaxe da linha 01, não é necessário utilizar o prefixo **random.** para executar o método **_randint( )_**. Podemos interpretar a instrução `from random import *` como, “do (módulo) random, importe tudo”. O asterisco em computação, geralmente, representa “tudo”, “todos”.

Ou podemos especificar quais funções importar com a mesma sintaxe.

```python
#Algoritmo - gerando 3 números inteiros aleatórios versão 3
from random import randint
for i in range(3):
    print(randint(1, 10))
```

### Usando **_alias_**

Podemos utilizar um “apelido” ou **_alias_** no nome das funções importadas.

```python
#Algoritmo - gerando 3 números inteiros aleatórios versão 4
from random import randint as rand
for i in range(3):
    print(rand(1, 10))
```

Também é permitido o uso de _alias_ em nome de módulo.

```python
#Algoritmo - gerando 3 números inteiros aleatórios versão 5
import random as r
for i in range(3):
    print(r.randint(1, 10))
```

### Mútiplos _imports_

Finalmente, para importarmos múltiplos módulos em um programa, basta separarmos os nomes por vírgula.

```python
#Múltiplos imports
import random, math, sys
```

## Módulo **_time_**

### Função **_asctime( )_**

A função **_asctime( )_** retorna a data e horário atual como uma **_string_**.

```python
#Usando função asctime()
>>> import time
>>> print(time.asctime())
Mon Apr 11 10:49:25 2022
```

Também podemos usar a **_asctime( )_** para gerar uma data e hora de uma _string_. Para isso, temos que fornecer uma tupla com 9 valores: o ano, dia, hora, minutos, segundos, dia da semana (0 é Segunda-Feira, 1 é Terça-Feira, …, 6 - Domingo), dia do ano (pode colocar ZERO), horário de verão (1 - SIM, 0 - NÃO).

Veja alguns exemplos.

```python
#Usando função asctime() com tuplas
>>> import time

>>> t = (2021, 12, 25, 10, 00, 30, 2, 0, 0)
>>> print(time.asctime(t))
Wed Dec 25 10:00:30 2021

>>> t = (2007, 7, 31, 15, 10, 45, 2, 0, 0)
>>> print(time.asctime(t))
Wed Jul 31 15:10:45 2007
```

### Função **_time( )_**

A função **_time()_** retorna a quantidade de segundos desde 01-Janeiro-1970 00:00:00 AM.

```python
#Usando função time()
>>> import time
>>> print(time.time())
1649667539.7314017
```

É bastante utilizado quando você quer saber o tempo de execução de um programa ou trecho de código.

```python
#Algoritmo - tempoSomar.py
import time

def somar(n):
    soma = 0
    for i in range(n):
        soma += i
    return soma

n = input("Digite quantidade de numeros para somar: ")
inicio = time.time()
print(somar(int(n)))
fim = time.time()
print("Tempo de execução: ", fim - inicio, " segundos")
```

Capturamos o tempo antes de executar a função **_somar( )_** na linha 10. E após a execução capturamos novamente o tempo.

Na linha 13 imprimimos a diferença no tempo de execução da função.

```
#Execução - tempoSomar.py
Digite quantidade de numeros para somar: 100000
4999950000
Tempo de execução:  0.003997802734375  segundos
```

### Função **_localtime( )_**

A função **_localtime()_** retorna um tupla com 9 valores locais: ano, mês, dia, hora, minutos, segundos, dia do ano e se é horário de verão.

```python
#Usando função localtime()
>>> import time

>>> time.localtime()
time.struct_time(tm_year=2022, tm_mon=4, tm_mday=11, tm_hour=11, tm_min=18, tm_sec=48, tm_wday=0, tm_yday=101, tm_isdst=1)

>>> t = time.localtime()
>>> print("Minutos ", t[4])
Minutos  18
>>> print("Dia do ano ", t[7])
Dia do ano  101
```

## Módulo **_copy_**

### Função copy( )

A função **_copy()_** cria uma cópia de uma lista ou dicionário, criando uma cópia distinta, alterando uma lista ou dicionário não afetará a outra.

```python
#Usando função copy()
>>> import copy
>>> lista_original = ["asa", "aviao"]
>>> lista_copia = copy.copy(lista_original)
>>> lista_copia.append("arara")
>>> lista_original
['asa', 'aviao']
>>> lista_copia
['asa', 'aviao', 'arara']
```

### Função deepcopy

A função **_deepcopy()_** é semelhante à função **_copy()_**, mas aplicado quando os elementos da lista ou do dicionário são outras listas ou dicionários.

```python
#Algoritmo - deepcopyAcao.py
import copy

turma_original = [
    {'nome': 'Ana', 'nota': 7.5},
    {'nome': 'Bia', 'nota': 8.5}
]

turma_copia = copy.deepcopy(turma_original)
turma_copia.pop()

print(turma_original)
print(turma_copia)
```

```python
#Execução - deepcopyAcao.py
[{'nome': 'Ana', 'nota': 7.5}, {'nome': 'Bia', 'nota': 8.5}]
[{'nome': 'Ana', 'nota': 7.5}]
```

## Módulo **_random_**

### Função **_random.choice()_**

A função **_choice()_** retorna um elemento aleatório da lista.

```python
#Usando função choice()
>>> import random
>>> lista_cor = ["preto", "branco", "vermelho"]

>>> random.choice(lista_cor)
'preto'
>>> random.choice(lista_cor)
'preto'
>>> random.choice(lista_cor)
'vermelho'
>>> random.choice(lista_cor)
'branco'
>>> random.choice(lista_cor)
'branco'
```

### Função **_random.shuffle()_**

A função **_shuffe()_** reordena os itens de uma lista.

```python
#Usando função shuffle()
>>> import random
>>> lista_cor = ["preto", "branco", "vermelho"]

>>> random.shuffle(lista_cor)
>>> print(lista_cor)
['vermelho', 'preto', 'branco']

>>> random.shuffle(lista_cor)
>>> print(lista_cor)
['branco', 'vermelho', 'preto']
```

## Módulo **_pprint_**

### Função pprint()

A função **_prprint()_** imprime um dicionário ordenando suas chaves.

```python
#Algoritmo - countCharPPrint.py
import pprint 

texto = input("Digite um texto: ")
count = {}
for letra in texto:
    count.setdefault(letra, 0)
    count[letra] += 1

pprint.pprint(count)
```

```python
#Execução - countCharPPrint.py
Digite um texto: abacaxi de morango
{' ': 2,
 'a': 4,
 'b': 1,
 'c': 1,
 'd': 1,
 'e': 1,
 'g': 1,
 'i': 1,
 'm': 1,
 'n': 1,
 'o': 2,
 'r': 1,
 'x': 1}
```

### Função pformat()

A função **_pformat()_** é semelhante ao **_pprint()_** mas ao invés de imprimir na tela, ele retorna uma **_string_** com as chaves do dicionários ordenadas.

```python
#Algoritmo - countCharPformat.py
import pprint 

texto = input("Digite um texto: ")
count = {}
for letra in texto:
    count.setdefault(letra, 0)
    count[letra] += 1

saida = pprint.pformat(count)
print(saida)
```

```
#Execução - countCharPformat.py
Digite um texto: bananada
{'a': 4, 'b': 1, 'd': 1, 'n': 2}
```

## Módulo **_sys_**

### Função **_sys.exit( )_**

A função **_sys.exit( )_** é usada para terminar a execução de um programa imediatamente.

É bastante utilizado quando queremos implementar a saída de um programa após o usuário digitar alguma **_string_** definida para terminar a execução do sistema. Por exemplo, enquanto o usuário não digitar **_sair_**, o código abaixo continuará executando:

```python
#Algoritmo - digite sair para terminar o programa
import sys
while True:
    print("Programa executando.")
    print("Digite 'sair' para terminar:")
    comando = input()
    if comando == 'sair':
        sys.exit()
```

No algoritmo acima, importamos o módulo **_sys_** (linha 01) para podermos utilizar a função **_exit( )_** na linha07.

Criamos um laço **_while_** infinito, nas linhas 02 a 07, que em cada iteração vai imprimir “Programa executando” e solicitar para que o usuário digite _“sair”_ para terminar o programa.

O valor digitado pelo usuário na linha 05 será armazenado na variável _comando_ e na linha 06, se o valor for igual à _“sair”_, a função **_exit( )_** será executada e o programa finalizado. Senão uma nova iteração será executada, repetindo o ciclo.

```
#Execução - digite sair para terminar o programa 
Programa executando.
Digite 'sair' para terminar:
teste
Programa executando.
Digite 'sair' para terminar:
nao
Programa executando.
Digite 'sair' para terminar:
quit
Programa executando.
Digite 'sair' para terminar:
sair
```

## Criando funções

Uma função é um pequeno algoritmo que resolve um problema específico, dentro do seu programa. Sua sintaxe é bem simples.

```python
#Sintaxe funções
def <nome_função>(<parâmetros>):
    instruções
```

Implemente a função abaixo que imprime uma mensagem na tela.

```python
#Algoritmo - ola.py
def ola():
    print("Minha funcao em Python")

ola()
```

A função está definida (**_def_**) na linha 01, com o nome **_ola_**. É uma função simples que imprime _“Minha funcao em Python”_.

Executamos uma função usando seu nome e parênteses, como ilustrado na linha 04.

```
#Execução - ola.py 
Minha funcao em Python
```

### Parâmetros de função (entrada)

Se quisermos fornecer valores de entrada para uma função, definimos entre os parênteses os **_parâmetros_**.  Implemente o exemplo, que verifica se um número é par.

```python
#Algoritmo - ehPar.py
def isEven(number):
    if (number % 2 == 0):
        print(f"{number} eh par")
    else:
        print(f"{number} nao eh par")

isEven(4)
isEven(9)
isEven(2124)
```

Na linha 01 definimos a assinatura da função **_isEven_**. A **assinatura do método** é representado pelo nome do método e os parâmetros de entrada.

Nas linhas 07, 08 e 09, chamamos a função **_isEven()_** com diferentes argumentos: 4, 9, 2124. Chamamos de **argumento** o valor do parâmetro de um método.

```
#Execução - ehPar.py 
4 eh par
9 nao eh par
2124 eh par
```

### Instrução **_return_** (saída)

Uma função pode retornar valor através do uso da instrução **_return_**. Se uma instrução **_return_** é executada, a função é encerrada.

Observe a função abaixo que executa uma operação aritmética e retorna o valor calculado.

```python
#Algoritmo - calcula.py
def calcula(x, y, op):
    if op == '+':
        return x + y	
    elif op == '-':
        return x - y
    elif op == '*':
        return x * y
    elif op == '/':
        return x / y
    else:
        return 'Operacao invalida'

comando = ""
while comando != "sair":
    comando = input("Calcular (S-sim)? (Digite 'sair' para encerrar.): ")
    if comando == "sair":
        print("Saindo...")
    else:
        x = int(input("Digite o primeiro numero: "))
        y = int(input("Digite o segundo numero: "))
        op = input("Digite a operacao (+, -, *, /): ")
        print(calcula(x, y, op))
```

Nas linhas 01 a 11 temos uma função que calcula uma operação (**_op_**) entre dois números **_x_** e **_y_**. Os três parâmetros da função.

Para cada operação executada, há uma cláusula **_return_**, linhas 03, 05, 07, 09, 11. Se o usuário selecionar a multiplicação, pela cláusula condicional, a linha 07 será executada e encerrará a função.

```
#Execução - calcula.py 
Calcular (S-sim)? (Digite 'sair' para encerrar.): s
Digite o primeiro numero: 12
Digite o segundo numero: 2
Digite a operacao (+, -, *, /): /
6.0
Calcular (S-sim)? (Digite 'sair' para encerrar.): s
Digite o primeiro numero: 33
Digite o segundo numero: 11
Digite a operacao (+, -, *, /): *
363
Calcular (S-sim)? (Digite 'sair' para encerrar.): sair
Saindo...
```

No jargão da computação, as funções que não retornam valores são denominadas **_procedimentos_**. 

Em **_Python_** uma função que não retorna um valor explicitamente com o **_return_**, implicitamente retona **_None_**.

```python
#Algoritmo - ehPar.py modificado
def isEven(number):
    if (number % 2 == 0):
        print(f"{number} eh par")
    else:
        print(f"{number} nao eh par")

num = isEven(4)
print(num)
```

A função **_isEven()_** não retorna valor, logo o valor da linha 07 será **_None._**

```
#Execução - ehPar.py modificado
4 eh par
None
```

## Escopo de variáveis

**_Escopo de variável_** é a região do código em que uma variável existe e pode ser utilizada. Em _Python_ temos o escopo **local** e o **global**.

**Variável local** é uma variável criada dentro de uma função, seu **escopo é local**, ela existirá somente dentro da função.

**Variável global** são as variáveis definidas fora de funções, possuem **escopo global**, podem ser utilizadas em qualquer local do código.

Desta forma, tenha em mente:
* Dentro de uma função podemos usar variáveis locais e _ler_ variáveis globais.
* Variáveis locais não podem ser usadas fora da função.
* Podemos ter variáveis locais e globais com o mesmo nome. Dentro de uma função as variáveis locais tem maior prioridade de uso.

Experimente os seguintes programas e entenda o escopo das variáveis.

```python
#Algoritmo - escopoGlobal.py
def imprime():
    print(contador)

contador = 10
imprime()
```

A variável da linha 04, pode ser acessada dentro do método _imprime()_.

```
#Execução - escopoGlobal.py
10
```

Não podemos acessar uma variável local fora do método.

```python
#Algoritmo - escopoLocalErro.py
def imprime():
    contador = 10

imprime()
print(contador)
```

Neste exemplo a variável _contador_ é local (linha 02) e não pode ser acessada fora do escopo do método, como na linha 05.

```
#Execução - escopoLocalErro.py
Traceback (most recent call last):
  File "c:\Python\codes\escopoLocalErro.py", line 5, in <module>
    print(contador)
NameError: name 'contador' is not defined
```

Variáveis de diferentes escopos mas com mesmo nome possuem a prioridade de localização, se chamadas dentro de uma função, a variável local será acessada.

```python
#Algoritmo - localEGlobal.py
def imprime():
    contador = 10
    print("valor em contador na funcao:", contador)

contador = 5
imprime()
print("valor em contador no escopo global:", contador)
```

Do exemplo anterior temos a variável _contador_ definida na linha 02 e na linha 05.

```
#Execução - localEGlobal.py
valor em contador na funcao: 10
valor em contador no escopo global: 5
```

### Palavra **_global_**

A palavra reservada **_global_** é aplicada na definição de variáveis de escopo global e permitem que o seu valor possa ser alterado dentro de funções.

```python
#Sintaxe - global
global <nome_variavel>
```

Execute o código abaixo em seu ambiente.

```python
#Algoritmo - usoGlobal.py
def incrementa(incremento):
    global contador
    contador = 10
    contador += incremento

incrementa(5)
print("valor em contador depois da função:", contador)
```

Nas linha 02 definimos a variável **_contador_** como **global**. Isto nos permite utilizá-lo em qualquer parte do código, inclusive fora da função como na linha 07 . A função **_incrementa( )_** recebe como parâmetro um valor **_incremento_** que será adicionado ao **_contador_**.

```python
#Execução - usoGlobal.py
valor em contador depois da função: 15
```

## Pilha de execução

Funções podem ter como instruções a chamada para outras funções. A linguagem Python consegue memorizar a execução das funções através de uma estrutura denominada pilha de execução.

Veja o exemplo abaixo, para efeitos didáticos.

```python
#Algoritmo - multiplasFuncoes.py
def funcaoA():
    print("Funcao A")

def funcaoB():
    print("Funcao B")
    funcaoA()

def funcaoC():
    print("Funcao C")
    funcaoB()
    print("Pilha de chamadas")

def main():
    funcaoC()

print("Inicio")
main()
print("Fim")
```

Temos 4 funções no algoritmo: _funcaoA()_ (linha 01), _funcaoB()_ (linha 04), _funcaoC()_ (linha 08), _main()_ (linha 13).

A _funcaoA()_ imprime uma mensagem na tela, a _funcaoB()_ além de imprimir uma mensagem, chama a _funcaoA()_. A _funcaoC()_ além de imprimir uma mensagem, executa a _funcaoB()_ e então imprime “Pilha de chamadas”.

Na execução do algoritmo, chamamos a _main()_ (linha 17) que simplesmente executa a _funcaoC()_.

```python
#Execução - multiplasFuncoes.py
Inicio
Funcao C
Funcao B
Funcao A
Pilha de chamadas
Fim
```

A linguagem cria uma pilha de chamadas, ilustrado abaixo, primeiro chamamos a função _main()_ no Passo 1, que executa a _funcaoC()_ (Passo 2) e coloca no topo da pilha.

A _funcaoC()_ invoca a _funcaoB()_ (Passo 3) que por sua vez chama a _funcaoA( )_ (Passo 4), sendo colocados no topo da pilha em cada passo. Dos passos 5 ao passo 7, a linguagem executa os métodos do topo da pilha, primeiro a _funcaoA( )_, depois a _funcaoB( )_ e finalmente a _funcaoC( )_ e _main( )_, respectivamente.

<table>
  <tr>
   <td colspan="7" ><code>Pilha de execução</code>
   </td>
  </tr>
  <tr>
   <td>
   </td>
   <td>
   </td>
   <td>
   </td>
   <td><code>funcaoA()</code>
   </td>
   <td>
   </td>
   <td>
   </td>
   <td>
   </td>
  </tr>
  <tr>
   <td>
   </td>
   <td>
   </td>
   <td><code>funcaoB()</code>
   </td>
   <td><code>funcaoB()</code>
   </td>
   <td><code>funcaoB()</code>
   </td>
   <td>
   </td>
   <td>
   </td>
  </tr>
  <tr>
   <td>
   </td>
   <td><code>funcaoC()</code>
   </td>
   <td><code>funcaoC()</code>
   </td>
   <td><code>funcaoC()</code>
   </td>
   <td><code>funcaoC()</code>
   </td>
   <td><code>funcaoC()</code>
   </td>
   <td>
   </td>
  </tr>
  <tr>
   <td><code>main()</code>
   </td>
   <td><code>main()</code>
   </td>
   <td><code>main()</code>
   </td>
   <td><code>main()</code>
   </td>
   <td><code>main()</code>
   </td>
   <td><code>main()</code>
   </td>
   <td><code>main()</code>
   </td>
  </tr>
  <tr>
   <td><code>Passo 1</code>
   </td>
   <td><code>Passo 2</code>
   </td>
   <td><code>Passo 3</code>
   </td>
   <td><code>Passo 4</code>
   </td>
   <td><code>Passo 5</code>
   </td>
   <td><code>Passo 6</code>
   </td>
   <td><code>Passo 7</code>
   </td>
  </tr>
</table>

## Função recursiva

Uma função é chamada **recursiva** quando ela invoca a si própria em seu bloco de instrução.

Para implementarmos uma função recursiva, é importante definirmos:
* O ponto de parada, ou caso base: condição que a função não chamará a sua execução recursiva.
* Execução recursiva: condição da execução recursiva.

Criar funções recursivas exige um pouco de experiência na programação, não se assuste se você não consegue neste momento implementar funções recursivas ou entender completamente os exemplos abaixo. Mas é importante saber que **toda função recursiva pode ser escrita em uma forma não recursiva.**

Nosso primeiro exemplo é implementar a operação da multiplicação **_mult( )_**, de números positivos, como função recursiva.

Uma multiplicação de **_x * y_** pode ser implementada como a soma de **_x_** por **_y_** vezes ou soma de **_y_** por **_x_** vezes. Por exemplo: 3 * 2 = 3 + 3 ou 3 * 2 = 2 + 2 + 2.

Como função recursiva, o caso base da multiplicação: qualquer número multiplicado por ZERO é ZERO, **_mult(x, 0) = 0_**.

Como execução recursiva: **_mult(x, y) = x + mult(x, y-1)_**.

```python
#Algoritmo - multRecursivo.py
def mult(x, y):
    if y == 0:
        return 0
    else:
        return x + mult(x, y - 1)

print(mult(5, 3))
print(mult(2, 0))
print(mult(4, 8))
```

Nas linhas 01-05 temos a função _mult(x, y)_ recebendo 2 parâmetros, e nas linhas 02-03 a condição base ou de parada e na linha 05 a chamada recursiva.

Para a execução **_mult(5, 3)_** sua pilha de execução ficará como abaixo.

<table>
  <tr>
   <td colspan="7" ><code>Pilha de execução recursiva</code>
   </td>
  </tr>
  <tr>
   <td>
   </td>
   <td>
   </td>
   <td>
   </td>
   <td><code>mult(5,0)</code>
   </td>
   <td>
   </td>
   <td>
   </td>
   <td>
   </td>
  </tr>
  <tr>
   <td>
   </td>
   <td>
   </td>
   <td><code>mult(5,1)</code>
   </td>
   <td><code>mult(5,1)</code>
   </td>
   <td><code>mult(5,1)</code>
   </td>
   <td>
   </td>
   <td>
   </td>
  </tr>
  <tr>
   <td>
   </td>
   <td><code>mult(5,2)</code>
   </td>
   <td><code>mult(5,2)</code>
   </td>
   <td><code>mult(5,2)</code>
   </td>
   <td><code>mult(5,2)</code>
   </td>
   <td><code>mult(5,2)</code>
   </td>
   <td>
   </td>
  </tr>
  <tr>
   <td><code>mult(5,3)</code>
   </td>
   <td><code>mult(5,3)</code>
   </td>
   <td><code>mult(5,3)</code>
   </td>
   <td><code>mult(5,3)</code>
   </td>
   <td><code>mult(5,3)</code>
   </td>
   <td><code>mult(5,3)</code>
   </td>
   <td><code>mult(5,3)</code>
   </td>
  </tr>
  <tr>
   <td><code>Passo 1</code>
   </td>
   <td><code>Passo 2</code>
   </td>
   <td><code>Passo 3</code>
   </td>
   <td><code>Passo 4</code>
   </td>
   <td><code>Passo 5</code>
   </td>
   <td><code>Passo 6</code>
   </td>
   <td><code>Passo 7</code>
   </td>
  </tr>
</table>

```
#Execução - multRecursivo.py
15
0
32
```

<table>
  <tr>
   <td>Agora é a sua vez!
   </td>
  </tr>
  <tr>
   <td>Implemente o cálculo do fatorial de um número, positivo, de forma recursiva.
<p>
O fatorial de um número é definido como:
<p>
<em>fatorial(n) = n * (n-1) * (n-2) * 1.</em>
<p>
Por exemplo:
<p>
<em>fatorial(5) = 5 * 4 * 3 * 2 * 1 = 120</em>
<p>
<em>fatorial(3) = 3 * 2 * 1 = 6</em>
<p>
<em>fatorial(1) = 1</em>
<p>
Identifique o caso base e a chamada recursiva.
   </td>
  </tr>
  <tr>
   <td>
   </td>
  </tr>
</table>

## Criando seus próprios módulos

Criar seus próprios módulos é bem simples, da mesma forma como outros programas em Python, você precisa salvar as suas funções em um arquivo **_.py_**.

Vamos implementar o seguinte exemplo!
* implementar um módulo com 3 funções:
    * implementar uma função que gera uma lista de números randômicos, em um intervalo.
    * implementar uma função que verifica se um número é par.
    * implementar uma função que verifica se um número é primo.
* criar um programa que solicite ao usuário a quantidade de números que quer gerar e verificar se é um número par e se é primo.

Para fins didáticos, salvei as funções em um módulo chamado **_meuModulo_**.

```python
#Arquivo - meuModulo.py
import random

def isEven(n):
    if n % 2 == 0:
        return True
    else:
        return False

def isPrime(n):
    if n == 1:
        return False
    elif n == 2:
        return True
    else:
        for i in range(2, n):
            if n % i == 0:
                return False
        return True

def generateRandomList(n, max = 100):
    lista = []
    for i in range(n):
        lista.append(random.randint(1, max))
    return lista
```

Em **_meuModulo.py_** temos 3 funções:
* isEven: verifica se um número é par, definido nas linhas 03 a 07.
* isPrime: verifica se um número é primo, ou seja, ele é divisível somente por 1 e por ele mesmo (linhas 09 a 18).
* generateRandomList: gera uma lista de tamanho _n_, entre os valores 1 e _max. _O parâmetro _max_ é opcional, tem como valor padrão 100. É necessário importar o módulo random (linha 01).

Para usarmos o nosso módulo, aplicamos a sintaxe _import_, como dos exemplos anteriores.

```python
#Arquivo - usoMeuModulo.py
import meuModulo

def processa(lista):
    for i in lista:
        if meuModulo.isPrime(i):
            print(i, "é primo")
        
        if meuModulo.isEven(i):
            print(i, "é par")
        else:
            print(i, "é impar")

n = input("Digite quantidade de numeros a serem gerados: ")
lista = meuModulo.generateRandomList(int(n))
processa(lista)
```

Em nosso programa importamos o _meuModulo_ (linha 01) e utilizamos suas funções nas linhas 05, 08, 14.

```
#Execução - usoMeuModulo.py
Digite quantidade de numeros a serem gerados: 5
46 é par
4 é par
10 é par
15 é impar
37 é primo
37 é impar
```

## Outros recursos das funções

### Argumentos nomeados

Vamos criar uma função que recebe o nome e a idade de uma pessoa, e imprime estas informações.

```python
#Algoritmo - nomeIdade.py
def informacoes(nome, idade):
    print("Nome: ", nome)
    print("Idade: ", idade)

informacoes("João", 20)
informacoes("Maria", 25)

#passando parametros pela posicao
informacoes(45, "Zezim")
```

Nas linhas 05 e 06 chamamos a função _informacoes(nome, idade)_ com os parâmetros na posição esperada.

Como a função não executa nenhuma operação numérica com a idade, podemos cometer um erro e mudar a ordem dos parâmetros, como na linha 09. 

O código ainda irá executar.

```
#Execução - nomeIdade.py
Nome:  João
Idade:  20 
Nome:  Maria
Idade:  25
Nome:  45
Idade:  Zezim
```

A forma como estamos passando os argumentos é denominada **_posicional_**. A posição do parâmetro determina o argumento associado.

Python fornece outra forma de enviar os argumentos para uma função, usando o nome do parâmetro e o valor de argumento na forma chave-valor. É o denominado **argumento nomeado**.

Modifique o código acima e execute-o novamente.

```python
#Algoritmo - nomeIdade.py modificado
def informacoes(nome, idade):
    print("Nome: ", nome)
    print("Idade: ", idade)

informacoes(nome = "Maria", idade = 25)
informacoes(idade = 45, nome = "Zezim")
```

Nas linhas 05 e 06 estamos indicando o nome do parâmetro e o argumento associado. Desta forma os valores serão tratados corretamente na execução da função.

```
#Execução - nomeIdade.py modificado
Nome:  Maria
Idade:  25
Nome:  Zezim
Idade:  45
```

### Parâmetros opcionais

Podemos definir um valor padrão para os parâmetros de uma função, desta forma não é obrigado fornecer valores na execução da função.

Modifique o código **_nomeIdade.py_** novamente e observe a sintaxe de valor padrão.

```python
#Algoritmo - nomeIdade.py com valor padrão
def informacoes(nome, idade = 10):
    print("Nome: ", nome)
    print("Idade: ", idade)

informacoes("Maria")
```

Na linha 01 a idade possui um valor padão de 10, não é mais obrigatório fornecer a idade na chamada da função, como ilustrado na linha 05.

```
#Execução - nomeIdade.py com valor padrão
Nome:  Maria
Idade:  10
```

Podemos inclusive deixar todos os parâmetros com valores padrões.

```python
#Algoritmo - nomeIdade.py todos os parâmetros com valores
def informacoes(nome = "Jose", idade = 10):
    print("Nome: ", nome)
    print("Idade: ", idade)

informacoes("Maria")
informacoes(20)
informacoes()
```

Observe a flexibilidade de usar parâmetros opcionais, nas linhas 05, 06 , 07.

```
#Execução - nomeIdade.py todos os parâmetros com valores
Nome:  Maria
Idade:  10
Nome:  20
Idade:  10
Nome:  Jose
Idade:  10
```

### Número de argumentos indefinidos

Podemos criar funções sem definir a quantidade de parâmetros, através da sintaxe abaixo.

```python
#Sintaxe - parâmetros indefinidos
def <funcao>(*<nome_parametro>):
```

O parâmetro **é interpretado como uma tupla**. Implemente o exemplo seguinte.

```python
#Algoritmo - parametrosIndefinidos.py
def imprime(* mensagem):
    print("Executando o metodo: imprime")
    print(mensagem)
    for m in mensagem:
        print(m)
    print("Fim do metodo: imprime")

imprime("Ola", "Mundo")
imprime("Python")
imprime("Python", "Java", "C++")
imprime()
```

Na linha 01 definimos **_mensagem_** como um parâmetro que recebe múltiplos valores, e nas linhas 08 a 11 utilizamos a função com uma quantidade variada de parâmetros.

```
#Execução - parametrosIndefinidos.py
Executando o metodo: imprime
('Ola', 'Mundo')
Ola
Mundo
Fim do metodo: imprime

Executando o metodo: imprime
('Python',)
Python
Fim do metodo: imprime

Executando o metodo: imprime
('Python', 'Java', 'C++')
Python
Java
C++
Fim do metodo: imprime

Executando o metodo: imprime
()
Fim do metodo: imprime
```

Se utilizarmos “**” em vez de somente “*”, o parâmetro **será interpretado como um dicionário** e poderá ter seus valores modificados. Você deve chamar a função com o formato **_chave-valor_**.

```
#Sintaxe - parâmetros indefinidos como dicionário
def <funcao>(**<nome_parametro>):
```

```python
#Algoritmo - parametrosIndefinidosDic.py
def criarpessoa(nome, idade, **informacoes):
    informacoes["nome"] = nome
    informacoes["idade"] = idade
    return informacoes

pessoa = criarpessoa("Maria", 25, cidade = "São Paulo", estado = "SP")
print(pessoa)
```

A função **_criarpessoa()_** utiliza a sintaxe **_informacoes_ como parâmetro, desta forma, dentro da função ele será interpretado como um dicionário.

```
#Execução - parametrosIndefinidosDic.py
{'cidade': 'São Paulo', 'estado': 'SP', 'nome': 'Maria', 'idade': 25}
```

## Erros comuns

Cuidado ao salvar o nome de seus programas e utilizar o nome de um módulo da linguagem. Como por exemplo **_random.py_**.

Se você importar o módulo **_random_**, o interpretador utilizará o programa que você criou em vez de usar o módulo **_random_** da linguagem.

Tome cuidado com os tipos de dados usados como parâmetros e forneça valores corretos, caso contrário teremos um erro no código.

```python
#Algoritmo - ehPar.py usando valores não numéricos
def isEven(number):
    if (number % 2 == 0):
        print(f"{number} eh par")
    else:
        print(f"{number} nao eh par")

isEven("4")
```

```
#Execução - ehPar.py usando valores não numéricos
Traceback (most recent call last):
  File "c:\Dropbox\Youtube\Python\codes\ehPar.py", line 7, in <module>
    isEven("4")
  File "c:\Dropbox\Youtube\Python\codes\ehPar.py", line 2, in isEven
    if (number % 2 == 0):
TypeError: not all arguments converted during string formatting
```

Como desenvolvedor, ao implementar funções recursivas, seja cauteloso em garantir que a pilha de chamadas chegará no caso base, senão teremos uma pilha infinita de execução. Pense de forma análoga à criação de um **_loop_**, implementando a condição de parada.

Teremos uma execução infinita se executarmos o **_mult( )_** com valor negativo. 

```python
#Algoritmo - multRecursivo.py
def mult(x, y):
    if y == 0:
        return 0
    else:
        return x + mult(x, y - 1)

print(mult(2, -1))
```

```
#Execução - multRecursivo.py em loop infinito
Traceback (most recent call last):
  File "c:\Python\codes\multRecursiva.py", line 7, in <module>
    print(mult(2, -1))
  File "c:\Python\codes\multRecursiva.py", line 5, in mult
    return x + mult(x, y - 1)
  File "c:\Python\codes\multRecursiva.py", line 5, in mult
    return x + mult(x, y - 1)
  File "c:\Python\codes\multRecursiva.py", line 5, in mult
    return x + mult(x, y - 1)
  [Previous line repeated 995 more times]
  File "c:\Python\codes\multRecursiva.py", line 2, in mult
    if y == 0:
RecursionError: maximum recursion depth exceeded in comparison
```

Lembre-se que variáveis locais não podem ser acessadas fora da função onde foram definidas.

```python
#Algoritmo - escopoLocalErro.py
def imprime():
    contador = 10

imprime()
print(contador)
```

```
#Execução - escopoLocalErro.py
Traceback (most recent call last):
  File "c:\Python\codes\escopoLocalErro.py", line 5, in <module>
    print(contador)
NameError: name 'contador' is not defined
```

E não podemos alterar o valor de variáveis globais que não são explicitamente marcadas com a palavra **_global_**.

```python
#Algoritmo - globalErro.py
def imprime():
    contador += 100
    print(contador)

contador = 10
imprime()
```

```
#Execução - globalErro.py
Traceback (most recent call last):
  File "c:\Python\codes\escopoGlobal.py", line 6, in <module>
    imprime()
  File "c:\Python\codes\escopoGlobal.py", line 2, in imprime
    contador += 100
UnboundLocalError: local variable 'contador' referenced before assignment
```

Os parâmetros opcionais devem sempre ser definidos após os parâmetros obrigatórios. O código abaixo não executa.

```python
#Algoritmo - erro no uso de parâmetros opcionais
def informacoes(nome = "Jose", idade):
    print("Nome: ", nome)
    print("Idade: ", idade)

informacoes("Maria", 20)
```

```
#Execução - erro no uso de parâmetros opcionais
  File "c:\codes\nomeIdade.py", line 1
    def informacoes(nome = "Jose", idade):
                                   ^^^^^
SyntaxError: non-default argument follows default argument
```