Embora eu nunca precisei disso, me ocorreu que criar um objeto imutável em Python poderia ser um pouco complicado. Você não pode simplesmente substituir __setattr__
, porque não pode nem mesmo definir atributos no arquivo __init__
. Subclassificar uma tupla é um truque que funciona:
class Immutable(tuple):
def __new__(cls, a, b):
return tuple.__new__(cls, (a, b))
@property
def a(self):
return self[0]
@property
def b(self):
return self[1]
def __str__(self):
return "<Immutable {0}, {1}>".format(self.a, self.b)
def __setattr__(self, *ignored):
raise NotImplementedError
def __delattr__(self, *ignored):
raise NotImplementedError
Mas então você tem acesso ao a
e b
variáveis através de self[0]
e self[1]
, o que é irritante.
Isso é possível no Pure Python? Caso contrário, como eu faria isso com uma extensão C?
(Respostas que funcionam apenas no Python 3 são aceitáveis).
Atualizar:
Então subclasse tupla é a maneira de fazê-lo em pura Python, que funciona bem, exceto para a possibilidade adicional de acessar os dados por [0]
, [1]
etc. Então, para completar esta questão tudo o que está faltando é howto fazê-lo "corretamente" em C, que Eu suspeito que seria bem simples, simplesmente não implementando nenhum geititem
ou setattribute
etc. Mas, em vez de fazer isso sozinho, ofereço uma recompensa por isso, porque sou preguiçoso. :)
fonte
.a
e.b
? Afinal, é para isso que as propriedades existem.NotImplemented
serve apenas como um valor de retorno para comparações completas. De__setatt__()
qualquer forma, um valor de retorno para é inútil, pois você geralmente não o verá. Código comoimmutable.x = 42
silenciosamente não fará nada. Você deve aumentar um emTypeError
vez disso.NotImplementedError
, masTypeError
é o que uma tupla aumenta se você tentar modificá-la.Respostas:
Outra solução que pensei: a maneira mais simples de obter o mesmo comportamento que o código original é
Ele não resolve o problema de que os atributos podem ser acessados via
[0]
etc., mas pelo menos é consideravelmente mais curto e oferece a vantagem adicional de ser compatível compickle
ecopy
.namedtuple
cria um tipo semelhante ao que eu descrevi nesta resposta , ou seja, derivadotuple
e usado__slots__
. Está disponível no Python 2.6 ou superior.fonte
verbose
parâmetro paranamedtuple
o código é facilmente gerado)) é que a única interface / implementação de umnamedtuple
é preferível a dezenas de interfaces / implementações manuscritas ligeiramente diferentes que faça quase a mesma coisa.namedtuple
copiado por valor quando passado por funções?A maneira mais fácil de fazer isso é usando
__slots__
:Instâncias de
A
agora são imutáveis, já que você não pode definir nenhum atributo nelas.Se você deseja que as instâncias da classe contenham dados, você pode combinar isso com a derivação de
tuple
:Editar : se você quiser se livrar da indexação, pode substituir
__getitem__()
:Observe que você não pode usar
operator.itemgetter
as propriedades neste caso, pois isso dependeria emPoint.__getitem__()
vez detuple.__getitem__()
. Além disso, isso não impedirá o usotuple.__getitem__(p, 0)
, mas mal posso imaginar como isso deve constituir um problema.Eu não acho que a maneira "certa" de criar um objeto imutável seja escrever uma extensão em C. O Python geralmente depende de implementadores de bibliotecas e usuários de bibliotecas aceitando adultos e, em vez de realmente impor uma interface, a interface deve ser claramente declarada na documentação. É por isso que não considero a possibilidade de contornar uma substituição substituindo-a
__setattr__()
porobject.__setattr__()
um problema. Se alguém faz isso, é por sua conta e risco.fonte
tuple
aqui__slots__ = ()
, em vez de__slots__ = []
? (Just esclarecendo)__slots__
não será alterado, certo? Seu objetivo é identificar por uma vez quais atributos podem ser definidos. Então, nãotuple
parece uma escolha muito natural nesse caso?__slots__
não consigo definir nenhum atributo. E se eu tiver__slots__ = ('a', 'b')
, os atributos aeb ainda são mutáveis.__setattr__
portanto é uma melhoria em relação à minha. 1 :)Você pode usar o Cython para criar um tipo de extensão para Python:
Ele funciona tanto em Python 2.xe em 3.
Testes
Se você não se importa com o suporte à indexação, é preferível
collections.namedtuple
sugerir @Sven Marnach :fonte
namedtuple
(ou mais precisamente do tipo retornado pela funçãonamedtuple()
) são imutáveis. Definitivamente.namedtuple
passa em todos os testes (exceto no suporte à indexação). Que requisito eu perdi?__weakref__
em Python?Outra idéia seria desabilitar completamente
__setattr__
e usarobject.__setattr__
no construtor:Claro que você pode usar
object.__setattr__(p, "x", 3)
para modificar umaPoint
instânciap
, mas sua implementação original sofre do mesmo problema (tentetuple.__setattr__(i, "x", 42)
umaImmutable
instância).Você pode aplicar o mesmo truque em sua implementação original: livrar-se
__getitem__()
e usartuple.__getitem__()
em suas funções de propriedade.fonte
__setattr__
, porque o objetivo não é ser infalível. O objetivo é deixar claro que não deve ser modificado e evitar modificações por engano.Você pode criar um
@immutable
decorador que substitua__setattr__
e mude__slots__
para uma lista vazia e decore o__init__
método com ele.Editar: como o OP observou, alterar o
__slots__
atributo apenas impede a criação de novos atributos , não a modificação.Edit2: Aqui está uma implementação:
Edit3: Using
__slots__
quebra este código, porque se interrompe a criação do objeto__dict__
. Estou procurando uma alternativa.Edit4: Bem, é isso. É um truque, mas funciona como um exercício :-)
fonte
object.__setattr__()
O que é isso? stackoverflow.com/questions/4828080/…Usando uma Dataclass congelada
Para o Python 3.7+, você pode usar uma Data Class com uma
frozen=True
opção , que é uma maneira muito pitonica e de manutenção de fazer o que você deseja.Seria algo assim:
Como dica de tipo é necessária para os campos das classes de dados, usei Qualquer um do
typing
módulo .Razões para NÃO usar um Namedtuple
Antes do Python 3.7, era frequente ver os nomeados serem usados como objetos imutáveis. Pode ser complicado de várias maneiras, uma delas é que o
__eq__
método entre os nomeados não considera as classes dos objetos. Por exemplo:Como você vê, mesmo que os tipos de
obj1
eobj2
sejam diferentes, mesmo que os nomes de seus campos sejam diferentes,obj1 == obj2
ainda dáTrue
. Isso__eq__
ocorre porque o método usado é o da tupla, que compara apenas os valores dos campos, dadas suas posições. Isso pode ser uma fonte enorme de erros, especialmente se você estiver subclassificando essas classes.fonte
Não acho que seja inteiramente possível, exceto usando uma tupla ou um nomeado. Não importa o que, se você substituir
__setattr__()
o usuário sempre poderá ignorá-lo ligandoobject.__setattr__()
diretamente. Qualquer solução que dependa__setattr__
é garantida para não funcionar.A seguir, é o mais próximo que você pode chegar sem usar algum tipo de tupla:
mas quebra se você se esforçar o suficiente:
mas o uso de Sven
namedtuple
é genuinamente imutável.Atualizar
Como a pergunta foi atualizada para perguntar como fazê-lo corretamente em C, eis a minha resposta sobre como fazê-lo corretamente no Cython:
Primeiro
immutable.pyx
:e a
setup.py
para compilá-lo (usando o comandosetup.py build_ext --inplace
:Então, experimente:
fonte
Eu criei classes imutáveis substituindo
__setattr__
e permitindo o conjunto se o chamador for__init__
:Isso ainda não é suficiente, pois permite que qualquer pessoa
___init__
altere o objeto, mas você entendeu.fonte
object.__setattr__()
O que é isso? stackoverflow.com/questions/4828080/…__init__
não seja muito satisfatório.Além das excelentes outras respostas, eu gostaria de adicionar um método para o python 3.4 (ou talvez 3.3). Esta resposta baseia-se em várias respostas anteriores para esta pergunta.
No python 3.4, você pode usar propriedades sem setters para criar membros da classe que não podem ser modificados. (Nas versões anteriores, era possível atribuir propriedades sem um setter.)
Você pode usá-lo assim:
que imprimirá
"constant"
Mas chamar
instance.a=10
causará:Explicação: propriedades sem setters são um recurso muito recente do python 3.4 (e acho que 3.3). Se você tentar atribuir a essa propriedade, um erro será gerado. Usando slots, restrinja as variáveis de membro para
__A_a
(o que é__a
).Problema:
_A__a
ainda é possível atribuir a (instance._A__a=2
). Mas se você atribuir a uma variável privada, a culpa é sua ...Essa resposta, entre outros, desencoraja o uso de
__slots__
. Usar outras maneiras de impedir a criação de atributos pode ser preferível.fonte
property
também está disponível no Python 2 (veja o código na própria pergunta). Ele não cria um objeto imutável, tente os testes da minha resposta , por exemplo,instance.b = 1
cria um novob
atributo.A().b = "foo"
ou seja, não permitir a configuração de novos atributos.Aqui está uma solução elegante :
Herdar desta classe, inicialize seus campos no construtor e está tudo pronto.
fonte
Se você estiver interessado em objetos com comportamento, o nomeado duplo é quase a sua solução.
Conforme descrito na parte inferior da documentação do nomeado , você pode derivar sua própria classe do nomeado; e, em seguida, você pode adicionar o comportamento desejado.
Por exemplo (código extraído diretamente da documentação ):
Isso resultará em:
Essa abordagem funciona para o Python 3 e o Python 2.7 (testados no IronPython também).
A única desvantagem é que a árvore de herança é um pouco estranha; mas isso não é algo com que você costuma brincar.
fonte
class Point(typing.NamedTuple):
As classes que herdam da
Immutable
classe a seguir são imutáveis, assim como suas instâncias, após o término da__init__
execução do método. Como é python puro, como outros já apontaram, não há nada que impeça alguém de usar os métodos especiais mutantes da baseobject
etype
, mas isso é suficiente para impedir que alguém mude uma classe / instância por acidente.Ele funciona seqüestrando o processo de criação de classe com uma metaclasse.
fonte
Eu precisava disso há pouco tempo e decidi fazer um pacote Python para ele. A versão inicial está no PyPI agora:
Usar:
Documentos completos aqui: https://github.com/theengineear/immutable
Espero que ajude, envolva um duplo nomeado conforme discutido, mas simplifique muito a instanciação.
fonte
Dessa forma, não para
object.__setattr__
de funcionar, mas ainda assim achei útil:pode ser necessário substituir mais itens (como
__setitem__
), dependendo do caso de uso.fonte
getattr
para fornecer um valor padrão parafrozen
. Isso simplificou um pouco as coisas. stackoverflow.com/a/22545808/5987__new__
substituição. Dentro__setattr__
basta substituir o condicional porif name != '_frozen' and getattr(self, "_frozen", False)
freeze()
função. O objeto será "congelado uma vez". Finalmente, preocupar-seobject.__setattr__
é bobagem, porque "somos todos adultos aqui".A partir do Python 3.7, você pode usar o
@dataclass
decorador da sua classe e ele será imutável como uma estrutura! No entanto, ele pode ou não adicionar um__hash__()
método à sua classe. Citar:Aqui está o exemplo dos documentos vinculados acima:
fonte
frozen
, ou seja@dataclass(frozen=True)
, mas basicamente bloqueia o uso de__setattr__
e__delattr__
como na maioria das outras respostas aqui. Ele apenas faz isso de uma maneira que seja compatível com as outras opções de classes de dados.Você pode substituir o setattr e ainda usar init para definir a variável. Você usaria a super classe setattr . aqui está o código
fonte
pass
vez deraise NotImplementedError
O
attr
módulo de terceiros fornece essa funcionalidade .Edit: python 3.7 adotou essa idéia no stdlib com
@dataclass
.attr
implementa classes congeladas substituindo__setattr__
e tem um impacto menor no desempenho a cada momento da instanciação, de acordo com a documentação.Se você tem o hábito de usar classes como tipos de dados,
attr
pode ser especialmente útil, pois cuida do clichê para você (mas não faz mágica). Em particular, ele escreve nove métodos dunder (__X__) para você (a menos que você desative qualquer um deles), incluindo repr, init, hash e todas as funções de comparação.attr
também fornece um auxiliar para__slots__
.fonte
Então, eu estou escrevendo o respectivo python 3:
I) com a ajuda do decorador da classe de dados e defina frozen = True. podemos criar objetos imutáveis em python.
para isso, é necessário importar a classe de dados das classes de dados lib e precisa configurar frozen = True
ex.
de classes de dados importar classe de dados
o / p:
Fonte: https://realpython.com/python-data-classes/
fonte
Uma abordagem alternativa é criar um wrapper que torne uma instância imutável.
Isso é útil em situações em que apenas algumas instâncias precisam ser imutáveis (como argumentos padrão de chamadas de função).
Também pode ser usado em fábricas imutáveis como:
Também protege
object.__setattr__
, mas é passível de outros truques devido à natureza dinâmica do Python.fonte
Eu usei a mesma idéia que Alex: uma meta-classe e um "marcador de inicialização", mas em combinação com sobrescrita __setattr__:
Nota: Estou chamando a meta-classe diretamente para fazê-la funcionar no Python 2.xe 3.x.
Também funciona com slots ...:
... e herança múltipla:
Observe, no entanto, que atributos mutáveis permanecem mutáveis:
fonte
Uma coisa que realmente não está incluída aqui é a imutabilidade total ... não apenas o objeto pai, mas também todas as crianças. tuplas / frozensets podem ser imutáveis, por exemplo, mas os objetos dos quais fazem parte podem não ser. Aqui está uma versão pequena (incompleta) que faz um trabalho decente ao impor a imutabilidade até o fim:
fonte
Você pode simplesmente substituir setAttr na declaração final do init. Então você pode construir, mas não mudar. Obviamente você ainda pode substituir por usint objeto. setAttr mas na prática a maioria das linguagens possui alguma forma de reflexão, de modo que a imutabilidade é sempre uma abstração com vazamento. Imutabilidade é mais para impedir que os clientes violem acidentalmente o contrato de um objeto. Eu uso:
=============================
A solução original oferecida estava incorreta, foi atualizada com base nos comentários usando a solução daqui
A solução original está errada de uma maneira interessante, por isso está incluída na parte inferior.
===============================
Resultado :
======================================
Implementação original:
Foi indicado nos comentários, corretamente, que isso realmente não funciona, pois impede a criação de mais de um objeto, pois você está substituindo o método setattr da classe, o que significa que um segundo não pode ser criado como self.a = will falha na segunda inicialização.
fonte
A solução básica abaixo aborda o seguinte cenário:
__init__()
pode ser gravado acessando os atributos como de costume.A idéia é substituir o
__setattr__
método e substituir sua implementação sempre que o status de congelamento do objeto for alterado.Então, precisamos de algum método (
_freeze
) que armazene essas duas implementações e alterne entre elas quando solicitado.Esse mecanismo pode ser implementado dentro da classe de usuário ou herdado de uma
Freezer
classe especial, como mostrado abaixo:fonte
Assim como um
dict
Eu tenho uma biblioteca de código aberto onde estou fazendo as coisas de maneira funcional , portanto, mover dados em um objeto imutável é útil. No entanto, não quero transformar meu objeto de dados para que o cliente interaja com eles. Então, eu vim com isso - ele fornece um ditado como objeto imutável + alguns métodos auxiliares.
Agradecemos a Sven Marnach em sua resposta pela implementação básica da restrição e atualização e exclusão de propriedades.
Métodos auxiliares
Exemplos
fonte