Às vezes uma aplicação precisa compartilhar um dicionário sem permitir que o consumidor altere sua estrutura diretamente. Copiar o objeto pode ser caro ou esconder atualizações feitas pelo proprietário. Expor o dicionário original, por outro lado, permite mudanças acidentais. types.MappingProxyType cria uma visão dinâmica e somente leitura de um mapping existente.
Neste guia, você aprenderá a criar proxies, entender a diferença entre imutabilidade e acesso somente leitura, compartilhar configurações, expor registros internos, trabalhar com objetos mutáveis aninhados, comparar com cópias e bibliotecas persistentes e evitar expectativas incorretas sobre segurança e concorrência.
O que é MappingProxyType?
MappingProxyType recebe um mapping e devolve um objeto que oferece operações de leitura, mas bloqueia alterações por meio do proxy.
from types import MappingProxyType
configuracao = {
"host": "localhost",
"porta": 8000,
}
publica = MappingProxyType(configuracao)
print(publica["host"])
# publica["porta"] = 9000 # TypeErrorO proxy implementa a interface de mapping: acesso por chave, iteração, len(), get(), keys(), values() e items(). Métodos de mutação, como atribuição, update() e pop(), não estão disponíveis.
Uma visão dinâmica, não uma cópia
O proxy continua ligado ao dicionário original. Se o proprietário modificar o objeto base, a mudança aparece na visão.
origem = {"modo": "teste"}
visao = MappingProxyType(origem)
print(visao["modo"]) # teste
origem["modo"] = "producao"
print(visao["modo"]) # producaoEssa característica é central. MappingProxyType não congela o dicionário; ele controla por qual referência a mutação pode ocorrer.
Somente leitura não significa imutabilidade profunda
O proxy protege a estrutura externa, mas não transforma valores aninhados em objetos imutáveis.
dados = {
"usuarios": ["Ana", "Bruno"],
"opcoes": {"debug": False},
}
visao = MappingProxyType(dados)
visao["usuarios"].append("Carla")
visao["opcoes"]["debug"] = True
print(dados)A lista e o dicionário internos continuam mutáveis. Para uma garantia profunda, normalize valores para tipos imutáveis, faça cópia profunda quando apropriado ou use estruturas persistentes projetadas para esse objetivo.
Expondo estado interno com segurança
Uma classe pode manter um dicionário mutável internamente e expor um proxy aos consumidores.
from collections.abc import Mapping
from types import MappingProxyType
class RegistroPlugins:
def __init__(self) -> None:
self._plugins: dict[str, object] = {}
self._publico = MappingProxyType(self._plugins)
@property
def plugins(self) -> Mapping[str, object]:
return self._publico
def registrar(self, nome: str, plugin: object) -> None:
if nome in self._plugins:
raise ValueError(f"plugin duplicado: {nome}")
self._plugins[nome] = pluginO consumidor pode listar e consultar plugins, mas precisa usar registrar() para alterar o estado. Isso preserva validações e invariantes da classe.
Por que anotar como Mapping?
Funções que apenas leem dados devem aceitar collections.abc.Mapping em vez de dict.
from collections.abc import Mapping
def montar_url(config: Mapping[str, object]) -> str:
host = str(config["host"])
porta = int(config["porta"])
return f"http://{host}:{porta}"Assim, a função aceita dicionários, proxies e outros mappings. A anotação comunica que mutação não faz parte do contrato.
Configurações públicas e privadas
Um padrão comum é construir uma configuração mutável durante a inicialização e expor uma visão de leitura depois.
_config = {
"timeout": 5.0,
"retries": 3,
"features": frozenset({"cache", "metricas"}),
}
CONFIG = MappingProxyType(_config)O nome em maiúsculas sugere constante, enquanto o proxy impede atribuições acidentais por consumidores. Contudo, código que ainda possui _config pode alterar os valores.
MappingProxyType e atributos de classe
O próprio Python usa um tipo de proxy para expor o namespace de classes em MinhaClasse.__dict__. Isso impede modificações diretas que ignorariam os mecanismos internos de atualização de classes.
class Exemplo:
valor = 10
print(type(Exemplo.__dict__))
print(Exemplo.__dict__["valor"])
# Exemplo.__dict__["valor"] = 20 # não permitido
Exemplo.valor = 20A atribuição correta passa pelo objeto classe, permitindo que o runtime mantenha caches e invariantes.
Comparação com dict.copy()
Uma cópia rasa produz um dicionário independente no nível externo.
origem = {"a": 1}
copia = origem.copy()
proxy = MappingProxyType(origem)
origem["a"] = 2
print(copia["a"]) # 1
print(proxy["a"]) # 2Use cópia quando o consumidor precisa de um snapshot independente. Use proxy quando deve observar atualizações do proprietário sem poder alterá-las pela referência exposta.
Snapshot somente leitura
Quando deseja estabilidade e proteção, combine cópia e proxy.
snapshot = MappingProxyType(dict(origem))O proxy protege a cópia externa contra mutação, e mudanças posteriores na origem não aparecem. Valores aninhados ainda são compartilhados se a cópia for rasa.
Desempenho e memória
Criar um proxy é barato porque não duplica todas as entradas. Isso pode ser útil para mappings grandes expostos a muitos consumidores. O acesso adiciona uma camada pequena de indireção, normalmente irrelevante diante do benefício de encapsulamento.
Não escolha MappingProxyType apenas por micro-otimização. O principal valor é tornar a intenção de leitura explícita e impedir mutações acidentais.
Concorrência e thread safety
Um proxy não torna o dicionário thread-safe. Se outra thread modifica a origem enquanto uma iteração ocorre, o consumidor pode observar estados intermediários ou receber erros como “dictionary changed size during iteration”.
for chave, valor in proxy.items():
... # a origem não deve mudar estruturalmente durante esta iteraçãoPara concorrência, use locks, snapshots, troca atômica de referências ou estruturas apropriadas. O proxy controla permissões da interface, não sincronização.
Serialização
Nem toda biblioteca aceita mappingproxy diretamente. Ao enviar para JSON ou APIs que exigem dict, converta explicitamente.
import json
texto = json.dumps(dict(proxy))A conversão cria uma cópia rasa. Se os valores contêm tipos não serializáveis, ainda será necessário normalizá-los.
Hash e uso como chave
Um proxy de mapping não deve ser tratado automaticamente como um objeto profundamente imutável e hashable. Mesmo que uma versão específica ofereça determinadas operações, a origem pode mudar. Para chaves estáveis, prefira uma representação imutável, como tupla ordenada de pares quando os valores também forem hashable.
chave = tuple(sorted(origem.items()))Erros comuns
- Acreditar que os valores ficam congelados: objetos aninhados continuam mutáveis.
- Esperar um snapshot: o proxy acompanha mudanças na origem.
- Usar como mecanismo de segurança: quem possui a referência original pode alterar tudo.
- Assumir thread safety: não há sincronização automática.
- Anotar consumidores como dict: prefira Mapping quando a função só lê.
- Converter repetidamente para dict: isso elimina a vantagem de evitar cópias.
Exemplo completo: catálogo versionado
from collections.abc import Mapping
from types import MappingProxyType
class Catalogo:
def __init__(self) -> None:
self._itens: dict[str, float] = {}
self._versao = 0
self._view = MappingProxyType(self._itens)
@property
def itens(self) -> Mapping[str, float]:
return self._view
@property
def versao(self) -> int:
return self._versao
def definir_preco(self, codigo: str, preco: float) -> None:
if preco < 0:
raise ValueError("preço negativo")
self._itens[codigo] = preco
self._versao += 1
catalogo = Catalogo()
catalogo.definir_preco("A1", 39.90)
print(catalogo.itens["A1"])
# catalogo.itens["A1"] = 0 # TypeErrorO catálogo mantém controle sobre atualizações, enquanto leitores observam o estado atual por uma interface não mutável.
Quando usar outra solução?
Use frozenset para conjuntos imutáveis, tuplas para sequências fixas, dataclasses congeladas para registros com atributos e bibliotecas de estruturas persistentes quando precisar de atualizações funcionais eficientes. Para configuração externa, valide e converta antes de expor.
Também é possível retornar uma cópia simples se o mapping for pequeno e o isolamento total for mais importante do que observar atualizações.
Conclusão
types.MappingProxyType cria uma visão dinâmica de somente leitura sobre um mapping. Ele é útil para encapsular dicionários internos, expor registros e configurações e comunicar que consumidores não devem alterar a estrutura.
A documentação oficial de MappingProxyType no módulo types descreve seu comportamento. Use o proxy sabendo que ele não oferece imutabilidade profunda, snapshot, segurança ou sincronização; essas garantias exigem decisões adicionais de arquitetura.







