Trilha de aprendizado · Nível 4 · Tutorial 2

Controlar o escopo e o estado das funções

Preveja quais nomes e alterações são visíveis dentro e fora de uma função e reduza a dependência de variáveis globais.

  • Nível: Iniciante
  • Duração: 20 min
  • 8 passos
Controlar o escopo e o estado das funções

O que você vai percorrer

  1. Onde cada nome existe Reconheça em que contexto os nomes de um programa são definidos: no corpo da função, no nível superior do arquivo ou entre os recursos embutidos do Python. 2 min
  2. Qual nome a função usa Use a prioridade local, global do arquivo e embutido para prever qual valor será encontrado quando nomes coincidem. 2 min
  3. O estado local em cada chamada Entenda por que os nomes locais são criados novamente a cada chamada e preveja o resultado de chamadas sucessivas. 2 min
  4. Por que ocorre UnboundLocalError Reconheça quando uma função tenta ler um nome local antes de atribuir um valor a ele e escolha correções que garantam um valor em todos os caminhos necessários. 3 min
  5. Reatribuir um parâmetro ou alterar o objeto Diferencie a reatribuição de um parâmetro da alteração do objeto mutável recebido pela função. 3 min
  6. O que a declaração global permite Use global para reatribuir um nome do arquivo e reconheça quando uma função passa a depender do histórico de chamadas. 2 min
  7. Trocar estado global por entradas e retornos Transforme uma função que altera um contador global em uma função que recebe o estado atual, calcula o próximo valor e o devolve ao chamador. 3 min
  8. Aplicação final: tornar os efeitos previsíveis Refatore um contador de tarefas para substituir estado global por parâmetro e retorno, preservando a mutação intencional de uma lista recebida. 3 min

O que você vai aprender

  • Identificar nomes locais, globais e embutidos em funções definidas no nível superior de um arquivo.
  • Prever os efeitos de atribuições locais e reconhecer situações que produzem UnboundLocalError.
  • Distinguir a reatribuição de um parâmetro da alteração do objeto mutável recebido.
  • Substituir uma alteração de estado global por parâmetros e valores de retorno explícitos.

Antes de começar

  • Definir funções com parâmetros e retorno
  • Controlar referências e cópias de coleções

Passo 1 de 8

Onde cada nome existe

Reconheça em que contexto os nomes de um programa são definidos: no corpo da função, no nível superior do arquivo ou entre os recursos embutidos do Python.

Três contextos para os nomes

Escopo indica onde o nome existe

Escopo é o contexto em que um nome pode ser consultado. Nos exemplos deste tutorial, com funções definidas diretamente no nível superior de um arquivo, observe três categorias:

  • Local: parâmetros e nomes que recebem valores dentro do corpo da função.
  • Global do arquivo: nomes definidos no nível superior, fora das funções. “Global” aqui se refere ao arquivo atual, não a todos os arquivos de um programa.
  • Embutido: nomes fornecidos pelo Python, como len e print, que você pode usar sem defini-los.

Mapa dos escopos

O corpo da função forma o contexto local; o restante do arquivo contém os nomes globais desse arquivo; e o Python disponibiliza nomes embutidos.

Diagrama com uma área de arquivo envolvendo uma área menor de função e, ao lado, uma caixa de ferramentas do Python conectada ao código.

Parâmetros e atribuições no corpo ficam na área local; definições no nível superior pertencem ao arquivo; recursos embutidos são fornecidos pelo Python.

Classifique pela origem

Um arquivo com três categorias de nomes

Observe onde cada nome é definido ou de onde ele vem.

python
prefixo = "Itens"

def resumir(nomes):
    quantidade = len(nomes)
    mensagem = f"{prefixo}: {quantidade}"
    print(mensagem)
    return quantidade

resultado = resumir(["caderno", "lápis"])

Mapa do exemplo

No nível superior do arquivo são definidos prefixo, resumir e resultado. Dentro da função, o parâmetro nomes e os alvos de atribuição quantidade e mensagem são locais. Já len e print são nomes embutidos fornecidos pelo Python.

Dica

Pergunta útil para classificar

Pergunte: “Esse nome foi definido no nível superior do arquivo, é um parâmetro ou recebeu um valor dentro da função, ou já é fornecido pelo Python?”

Associe cada nome ao escopo

Classificação dos nomes

Com base no código anterior, associe cada nome à sua categoria.

Toque em um item e depois no par correspondente.

Passo 2 de 8

Qual nome a função usa

Use a prioridade local, global do arquivo e embutido para prever qual valor será encontrado quando nomes coincidem.

A busca para na primeira associação

Prioridade dos nomes

Nas funções não aninhadas deste tutorial, o Python procura um nome nesta ordem: local → global do arquivo → embutido. A busca termina na primeira associação encontrada.

Um parâmetro ou outro nome local pode ocultar um global de mesmo nome. Se não houver associação local, a função pode consultar o global. Se também não houver global, nomes embutidos como print e len continuam disponíveis.

Caminho da busca

Diagrama com a busca partindo do corpo da função, passando pelo nível global do arquivo e chegando aos recursos embutidos, com destaque para a primeira correspondência.

A busca segue do local para o global e depois para os embutidos, parando assim que encontra o nome.

Local e global com o mesmo nome

O parâmetro oculta o global

Observe os valores exibidos dentro e fora da função.

python
tema = "global"

def mostrar(tema):
    origem = "local"
    print(tema, origem)

mostrar("argumento")
print(tema)

# Saída:
# argumento local
# global

Dentro de mostrar, o parâmetro local tema é encontrado antes do global de mesmo nome. Fora da função, tema continua associado a "global".

O nome print não foi definido local nem globalmente, então a busca chega à função embutida.

Uma atribuição cria outra associação local

A associação local não substitui a associação global de mesmo nome.

python
cor = "azul"

def escolher_cor():
    cor = "verde"
    return cor

print(escolher_cor())  # verde
print(cor)             # azul

Evite ocultar nomes embutidos

Um global também pode ocultar um embutido

Neste exemplo, a associação global chamada len é encontrada antes do recurso embutido de mesmo nome.

python
len = 4

def mostrar_limite():
    return len

print(mostrar_limite())  # 4

Dica

Prefira nomes descritivos

Evite usar nomes como len e print para suas variáveis. Nomes como limite, quantidade e mensagem preservam o acesso aos recursos embutidos e deixam a intenção mais clara.

Preveja as duas saídas

Qual será a saída?

Considere o código:

rotulo = "fora"

def exibir(rotulo):
    local = "dentro"
    print(rotulo, local)

exibir("argumento")
print(rotulo)

Quais linhas serão exibidas, nessa ordem?

Passo 3 de 8

O estado local em cada chamada

Entenda por que os nomes locais são criados novamente a cada chamada e preveja o resultado de chamadas sucessivas.

Um novo estado local por chamada

Cada chamada de uma função estabelece suas próprias associações locais. Os parâmetros recebem os objetos enviados como argumentos naquela chamada, e as atribuições do corpo criam valores locais para essa execução.

Por isso, um contador inicializado dentro da função começa novamente do valor definido no corpo. A chamada seguinte não continua automaticamente o contador da chamada anterior.

Duas chamadas independentes

Em cada chamada, etiqueta recebe o argumento atual e contador começa em 0.

python
def registrar(etiqueta):
    contador = 0
    contador = contador + 1
    return f"{etiqueta}: {contador}"

primeiro = registrar("A")
segundo = registrar("B")

print(primeiro)
print(segundo)

Compare as duas chamadas

Estados locais separados

Diagrama com duas chamadas independentes da função registrar. Na primeira, o parâmetro recebe A e o contador vai de zero para um. Na segunda, o parâmetro recebe B e outro contador também vai de zero para um.

Cada quadro representa uma chamada. A segunda cria suas próprias associações locais, em vez de reutilizar as da primeira.

Dica

O que permanece após o retorno?

O valor retornado pode ser guardado pelo chamador, como em primeiro e segundo. Isso não transforma contador em um nome global: esse nome continua sendo local a cada chamada.

Preveja as duas chamadas

Observe que passos é inicializado dentro da função.

python
def contar(categoria):
    passos = 0
    passos = passos + 1
    return passos

resultado_a = contar("estudo")
resultado_b = contar("lazer")

print(resultado_a, resultado_b)

Complete a saída

A única linha exibida pelo código é ____.

Passo 4 de 8

Por que ocorre UnboundLocalError

Reconheça quando uma função tenta ler um nome local antes de atribuir um valor a ele e escolha correções que garantam um valor em todos os caminhos necessários.

A atribuição define o nome como local

A ordem das linhas não muda a classificação

Se o corpo de uma função atribui a um nome, esse nome é considerado local em toda a função nos exemplos deste tutorial. Isso vale até para as linhas anteriores à atribuição.

Ao tentar ler esse nome antes que ele receba um valor na chamada atual, Python gera UnboundLocalError. Um nome global com o mesmo texto não é usado como alternativa: a busca já determinou que o nome é local.

Leitura antes da atribuição

A primeira instrução do corpo tenta ler saldo. Porém, a atribuição posterior faz saldo ser local em toda a função.

python
saldo = 100

def ajustar():
    print(saldo)       # tenta ler o saldo local
    saldo = saldo + 10
    return saldo

ajustar()  # UnboundLocalError

Classificação e execução são etapas diferentes

O nome é classificado como local por causa da atribuição presente no corpo. Durante a execução, a leitura chega antes de esse espaço local receber um valor.

Diagrama com saldo global igual a 100 fora da função e um espaço local de saldo ainda vazio dentro dela. Uma linha do tempo mostra primeiro a tentativa de leitura e depois a atribuição, com a leitura bloqueada antes de alcançar o valor global.

A presença do global saldo = 100 não preenche o saldo local ainda vazio.

O problema também aparece em caminhos condicionais

Atribuir em apenas um ramo não basta

Uma variável local precisa receber um valor antes da leitura em cada caminho que possa chegar a essa leitura. No código abaixo, valor é atribuído quando ativo é verdadeiro, mas permanece sem valor quando o argumento é False.

Um caminho deixa o nome sem valor

A falha acontece em print(valor) na chamada com False.

python
limite = 10

def escolher(ativo):
    if ativo:
        valor = limite + 5

    print(valor)
    return valor

escolher(False)  # UnboundLocalError

Duas estratégias de correção

A primeira função garante uma atribuição antes da condição. A segunda usa nomes diferentes para o global consultado e para o resultado local.

python
limite = 10

def escolher(ativo):
    valor = limite
    if ativo:
        valor = limite + 5
    return valor


desconto = 10

def calcular_preco(preco):
    desconto_aplicado = desconto + 5
    return preco - desconto_aplicado

Atenção

Não basta deslocar a leitura

Mover um print ou um return para outra linha só corrige o problema se todos os caminhos que chegam à nova posição já tiverem atribuído um valor ao nome local.

Encontre a primeira leitura inválida

Diagnóstico do erro

Considere a chamada preparar(False). Qual operação falha primeiro, e por quê?

base = 20

def preparar(aplicar):
    print(base)
    if aplicar:
        base = 25
    return base

preparar(False)

Escolha uma correção completa

Garanta um valor nos dois caminhos

A intenção é devolver valor quando adicional for falso e devolver valor + taxa quando for verdadeiro. Qual alteração corrige a função para as duas situações?

taxa = 2

def calcular(valor, adicional):
    if adicional:
        resultado = valor + taxa
    print(resultado)
    return resultado

Passo 5 de 8

Reatribuir um parâmetro ou alterar o objeto

Diferencie a reatribuição de um parâmetro da alteração do objeto mutável recebido pela função.

O parâmetro aponta para o objeto recebido

Nome local, objeto compartilhado

Um parâmetro é um nome local associado ao objeto fornecido como argumento. Se o argumento for uma lista, a função não cria uma cópia automática.

Por isso, duas operações têm efeitos diferentes:

  • itens = ["novo"] faz o parâmetro apontar para outra lista. O nome do chamador continua apontando para a lista original.
  • itens.append("novo") ou itens[0] = "novo" modifica a lista compartilhada. O chamador observa a alteração.

Reatribuição e mutação

Compare os caminhos das referências antes e depois de cada operação.

Dois diagramas de referências. No primeiro, o parâmetro passa a apontar para uma nova lista e o nome do chamador permanece ligado à lista original. No segundo, parâmetro e nome do chamador continuam apontando para a mesma lista modificada.

Reatribuir muda a seta do nome local; mutar altera o objeto apontado pelas setas compartilhadas.

Compare os efeitos no chamador

Duas funções, dois efeitos

Execute o código no seu ambiente e compare as duas listas exibidas.

python
def substituir(itens):
    itens = ["novo"]
    return itens


def acrescentar(itens):
    itens.append("novo")


primeira = ["a", "b"]
resultado = substituir(primeira)
print(primeira)    # ['a', 'b']
print(resultado)   # ['novo']

segunda = ["a", "b"]
retorno = acrescentar(segunda)
print(segunda)     # ['a', 'b', 'novo']
print(retorno)     # None

Retorno e mutação são efeitos distintos

Em substituir, a reatribuição afeta somente o parâmetro local. O return permite obter a nova lista, mas não altera a lista original.

Em acrescentar, não há return explícito, mas a mutação permanece visível no chamador. Da mesma forma, adicionar um return depois de append não desfaria a alteração já feita.

Classifique cada operação

Operação e efeito

Associe cada instrução executada dentro da função ao seu efeito.

Toque em um item e depois no par correspondente.

Preveja as duas listas

Mutação seguida de reatribuição

Antes de executar, preveja as duas linhas produzidas pelos print.

python
def atualizar(registros):
    registros[0] = "feito"
    registros = ["interno"]
    registros.append("extra")
    return registros


tarefas = ["pendente", "revisar"]
resultado = atualizar(tarefas)

print(tarefas)
print(resultado)

Explique sua previsão

Quais valores serão exibidos para tarefas e resultado? Justifique usando as referências antes e depois da reatribuição do parâmetro.

Escreva pelo menos 60 caracteres (0/60).

Passo 6 de 8

O que a declaração global permite

Use global para reatribuir um nome do arquivo e reconheça quando uma função passa a depender do histórico de chamadas.

Reatribuir o nome do arquivo

Para que serve global

Dentro de uma função, global contador indica que o nome contador se refere ao nome definido no nível superior do arquivo. Isso permite reatribuir esse nome, em vez de criar uma associação local.

A declaração deve aparecer antes das leituras e atribuições a esse nome no corpo da função.

Um contador compartilhado

Execute o código e observe que as duas chamadas atualizam o mesmo nome global.

python
contador = 0

def registrar_chamada():
    global contador
    contador = contador + 1
    return contador

print(registrar_chamada())  # 1
print(registrar_chamada())  # 2
print(contador)             # 2

Local por chamada ou global compartilhado

O valor anterior pode permanecer

Um contador local inicializado no corpo recomeça em cada chamada. Já o contador declarado como global referencia uma única associação compartilhada pelas chamadas feitas naquele arquivo. Por isso, cada atualização global parte do estado deixado anteriormente.

Dois modelos de estado

Compare as referências: no modelo local, cada chamada possui seu próprio contador; no global, todas chegam ao mesmo contador externo.

Comparação entre duas chamadas com contadores locais separados e duas chamadas conectadas a um único contador global compartilhado.

À esquerda, estados locais independentes. À direita, um estado global compartilhado e atualizado em sequência.

Quando global não é necessário

Leitura, mutação e reatribuição

Apenas consultar um nome global não exige global. Também não é necessário declarar global para modificar um objeto mutável encontrado por esse nome, desde que o próprio nome não seja reatribuído.

No exemplo, taxa é somente lido e a lista encontrada por historico é modificada. Nenhum dos dois nomes recebe uma nova associação dentro das funções.

Consulta e mutação sem global

python
taxa = 2
historico = []

def calcular(valor):
    return valor * taxa

def registrar(nome):
    historico.append(nome)

print(calcular(5))  # 10
registrar("Ana")
print(historico)    # ['Ana']

# Estas reatribuições exigiriam global nas respectivas funções:
# taxa = 3
# historico = []

Atenção

Dependência implícita

Uma função que lê ou reatribui estado global pode produzir resultados diferentes com os mesmos argumentos, dependendo do valor anterior e da ordem das chamadas. A mutação de um objeto global também deixa um efeito compartilhado, mesmo sem a declaração global.

Verifique a regra

Qual caso exige global?

Considere que todos os nomes citados já foram definidos no nível superior do arquivo. Em qual corpo de função a declaração global é necessária?

O histórico das chamadas importa?

Se um contador global começa em 0 e uma função com global contador soma 1 a cada chamada, a segunda chamada parte do valor deixado pela primeira.

Passo 7 de 8

Trocar estado global por entradas e retornos

Transforme uma função que altera um contador global em uma função que recebe o estado atual, calcula o próximo valor e o devolve ao chamador.

Encontre o estado implícito

O que entra e o que sai sem aparecer na chamada?

Nesta versão, vender lê o valor anterior de estoque e reatribui esse mesmo nome global. Embora a chamada mostre apenas quantidade, o resultado também depende do estado deixado pelas chamadas anteriores.

Para tornar essa dependência explícita, o valor atual do estoque deve entrar como argumento, e o novo valor deve sair pelo return.

Versão dependente de estado global

A função lê e reatribui estoque, mesmo que esse estado não apareça em sua lista de parâmetros.

python
estoque = 10


def vender(quantidade):
    global estoque
    estoque = estoque - quantidade
    return estoque


print(vender(3))  # 7
print(vender(2))  # 5

Atenção

Não basta apagar global

Se apenas removermos global estoque, a expressão estoque - quantidade tentará ler um nome local antes de sua atribuição e causará UnboundLocalError. A refatoração precisa também receber o estado atual por parâmetro, devolvê-lo atualizado e guardar o retorno no código chamador.

Torne o fluxo explícito

Entrada, cálculo, retorno e atribuição

Na versão refatorada, estoque_atual recebe o estado usado naquela chamada. A função calcula novo_estoque e o retorna, mas não decide onde esse resultado será guardado. Essa atribuição fica visível no chamador: estoque_a = vender(3, estoque_a).

Como não existe um único contador global compartilhado, a mesma função pode atualizar estoque_a e estoque_b de forma independente.

Comparação dos fluxos de estado

À esquerda, várias chamadas alcançam diretamente um único estado externo. À direita, cada valor entra na função como argumento e retorna ao respectivo nome no chamador.

Comparação entre chamadas ligadas a um contador global compartilhado e dois fluxos independentes de argumento e retorno.

No fluxo explícito, cada chamador fornece o estado atual e escolhe onde guardar o resultado retornado.

Implementação refatorada completa

Copie e execute este código no seu ambiente. A definição completa de vender funciona com qualquer estoque fornecido pelo chamador.

python
def vender(quantidade, estoque_atual):
    novo_estoque = estoque_atual - quantidade
    return novo_estoque


estoque_a = 10
estoque_b = 20

estoque_a = vender(3, estoque_a)
estoque_b = vender(5, estoque_b)

print(estoque_a)  # 7
print(estoque_b)  # 15

Complete a refatoração

1. Receba o estado atual

Complete o segundo parâmetro:

def vender(quantidade, ____):
    novo_estoque = estoque_atual - quantidade
    return novo_estoque

2. Calcule o novo estado

Complete o cálculo local:

def vender(quantidade, estoque_atual):
    novo_estoque = ____
    return novo_estoque

3. Devolva o resultado

Complete o retorno:

def vender(quantidade, estoque_atual):
    novo_estoque = estoque_atual - quantidade
    return ____

4. Guarde o retorno no chamador

Complete a atualização explícita. Depois dessa instrução, estoque deve valer 8.

estoque = 12
estoque = ____

Passo 8 de 8

Aplicação final: tornar os efeitos previsíveis

Refatore um contador de tarefas para substituir estado global por parâmetro e retorno, preservando a mutação intencional de uma lista recebida.

Diagnóstico do programa

Quatro perguntas para revisar os efeitos

Ao analisar uma função, verifique: 1) onde cada nome foi definido; 2) se um nome local recebe valor antes de ser lido; 3) se a operação reatribui um nome ou modifica um objeto; 4) se o resultado depende de estado global.

No programa abaixo, tarefas, total_tarefas e registrar são nomes globais do arquivo. titulo e registros são parâmetros locais. A declaração global faz total_tarefas, dentro da função, referir-se ao nome global. Já print é um nome embutido.

A função produz dois efeitos diferentes: reatribui o contador global e modifica, por meio de registros, a mesma lista encontrada pelo nome tarefas no código chamador.

Versão com estado global

Copie este programa completo para um arquivo Python no seu computador. Antes de executá-lo, preveja as duas linhas finais.

python
total_tarefas = 0
tarefas = []


def registrar(titulo, registros):
    global total_tarefas
    registros.append(titulo)
    total_tarefas = total_tarefas + 1
    return total_tarefas


registrar("Estudar escopo", tarefas)
registrar("Revisar funções", tarefas)

print(total_tarefas)
print(tarefas)

Faça a refatoração

Transforme o estado implícito em fluxo explícito

No seu editor, refatore o programa antes de consultar a solução de referência. Sua versão deve atender a todos estes critérios:

  1. Remover a declaração global.
  2. Adicionar à função um parâmetro que receba o total atual.
  3. Calcular o novo total em um nome local.
  4. Retornar esse novo total.
  5. No código chamador, guardar em total_tarefas o retorno de cada chamada.
  6. Manter registros.append(titulo), pois a alteração da lista é intencional.

Execute a versão refatorada com as mesmas duas tarefas e confira as linhas exibidas.

Relate o resultado da sua execução

Quais foram as duas linhas exibidas pela sua versão refatorada? Explique também por que tarefas continua mudando mesmo depois da remoção de global.

Escreva pelo menos 60 caracteres (0/60).

Compare com a solução de referência

Contador explícito e lista compartilhada

Compare esta solução com a sua. Aqui, total_atual e novo_total são nomes locais, assim como os parâmetros titulo e registros.

python
def registrar(titulo, registros, total_atual):
    registros.append(titulo)
    novo_total = total_atual + 1
    return novo_total


total_tarefas = 0
tarefas = []

total_tarefas = registrar("Estudar escopo", tarefas, total_tarefas)
total_tarefas = registrar("Revisar funções", tarefas, total_tarefas)

print(total_tarefas)
print(tarefas)

# Saída esperada:
# 2
# ['Estudar escopo', 'Revisar funções']

Dois efeitos, dois caminhos

O contador percorre um fluxo explícito de entrada e retorno. A lista, por sua vez, é um objeto compartilhado que recebe novos elementos.

Comparação visual entre um contador que entra e retorna pela função e uma lista compartilhada que é modificada durante duas chamadas.

O retorno não modifica sozinho o contador do chamador: cada resultado precisa ser atribuído. A mutação da lista já permanece visível no objeto compartilhado.

Conferência dos efeitos

Na primeira chamada, o contador entra como 0 e o retorno 1 é guardado em total_tarefas. Na segunda, esse 1 entra como argumento e o retorno 2 é guardado novamente.

Nas duas chamadas, registros permite alcançar a mesma lista. Cada append altera esse objeto. Portanto, retirar o estado global do contador não elimina a mutação intencional da lista — apenas torna explícito como o contador é atualizado.

Revisão final

Resumo

Escopo não é o mesmo que ausência de efeitos

Use este resumo para prever o comportamento de outras funções.

  • Em funções não aninhadas deste tutorial, a busca considera nomes locais, globais do arquivo e embutidos.
  • Um nome local precisa receber valor antes de ser lido em todo caminho que alcance a leitura.
  • Reatribuir um parâmetro muda sua associação local; modificar o objeto recebido pode ser visível ao chamador.
  • Estado global cria dependências implícitas do valor anterior e da ordem das chamadas.
  • Parâmetros e retornos tornam explícito o fluxo de um contador entre chamadas.
  • Nomes locais não tornam automaticamente locais todos os efeitos de uma função.

Tutorial concluído

Parabéns! Você concluiu: Controlar o escopo e o estado das funções

Concluído! Agora você pode diagnosticar a origem dos nomes, distinguir reatribuição de mutação e substituir dependências globais por entradas e retornos explícitos.

80 XP

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso