Pergunta

Eu quero ser capaz de criar uma classe (em Python) que uma vez inicializado com __init__, não aceita novos atributos, mas aceita modificações dos atributos existentes.Há várias hack-ish maneiras que eu posso ver para fazer isso, por exemplo, tendo uma __setattr__ método de como

def __setattr__(self, attribute, value):
    if not attribute in self.__dict__:
        print "Cannot set %s" % attribute
    else:
        self.__dict__[attribute] = value

e, em seguida, a edição de __dict__ diretamente dentro __init__, mas eu queria saber se existe um 'bom' maneira de fazer isso?

Foi útil?

Solução

Eu não usaria __dict__ diretamente, mas você pode adicionar uma função para "congelar" explicitamente uma instância:

class FrozenClass(object):
    __isfrozen = False
    def __setattr__(self, key, value):
        if self.__isfrozen and not hasattr(self, key):
            raise TypeError( "%r is a frozen class" % self )
        object.__setattr__(self, key, value)

    def _freeze(self):
        self.__isfrozen = True

class Test(FrozenClass):
    def __init__(self):
        self.x = 42#
        self.y = 2**3

        self._freeze() # no new attributes after this point.

a,b = Test(), Test()
a.x = 10
b.z = 10 # fails

Outras dicas

Se alguém está interessado em fazer isso com um decorador, aqui está uma solução funcional:

from functools import wraps

def froze_it(cls):
    cls.__frozen = False

    def frozensetattr(self, key, value):
        if self.__frozen and not hasattr(self, key):
            print("Class {} is frozen. Cannot set {} = {}"
                  .format(cls.__name__, key, value))
        else:
            object.__setattr__(self, key, value)

    def init_decorator(func):
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            func(self, *args, **kwargs)
            self.__frozen = True
        return wrapper

    cls.__setattr__ = frozensetattr
    cls.__init__ = init_decorator(cls.__init__)

    return cls

Muito simples de usar:

@froze_it 
class Foo(object):
    def __init__(self):
        self.bar = 10

foo = Foo()
foo.bar = 42
foo.foobar = "no way"

Resultado:

>>> Class Foo is frozen. Cannot set foobar = no way

Na verdade, você não quer __setattr__, você quer __slots__. Adicionar __slots__ = ('foo', 'bar', 'baz') Para o corpo da classe, e Python garantirá que haja apenas foo, bar e baz em qualquer instância. Mas leia as advertências nas listas de documentação!

Slots é o caminho a percorrer:

O pythonic maneira é a utilização de slots em vez de brincar com o __setter__.Embora possa resolve-se o problema de não dar qualquer melhoria de desempenho.Os atributos de objetos são armazenadas em um dicionário "__dict__"esta é a razão por que você pode adicionar dinamicamente atributos para objetos de classes que criamos até agora.Usando um dicionário para o atributo de armazenamento é muito conveniente, mas pode significar um desperdício de espaço para objetos, que tem apenas uma pequena quantidade de variáveis de instância.

Slots são uma boa maneira de contornar este espaço de problema de consumo.Em vez de ter uma dinâmica dict que permite a adição de atributos para os objetos dinamicamente, slots de fornecer uma estrutura estática que proíbe acréscimos após a criação de uma instância.

Quando criamos uma classe, podemos usar slots para evitar a criação dinâmica de atributos.Para definir slots, você tem de definir uma lista com o nome __slots__.A lista deve conter todos os atributos que você deseja usar.Vamos demonstrar isso na aula seguinte, em que os slots lista contém apenas o nome de um atributo "val".

class S(object):

    __slots__ = ['val']

    def __init__(self, v):
        self.val = v


x = S(42)
print(x.val)

x.new = "not possible"

=> Ele não consegue criar um atributo de "novo":

42 
Traceback (most recent call last):
  File "slots_ex.py", line 12, in <module>
    x.new = "not possible"
AttributeError: 'S' object has no attribute 'new'

NB:

  1. Desde Python 3.3 a vantagem de otimizar o consumo de espaço não é tão impressionante mais.Com O Python 3.3 Chave De Partilha Os dicionários são usados para o armazenamento de objetos.Os atributos das instâncias são capazes de compartilhar parte de sua memória de armazenamento interno entre si, i.e.a parte que armazena as chaves e os seus respectivos hashes.Isso ajuda a reduzir o consumo de memória dos programas, o que cria várias instâncias de não-builtin tipos.Mas ainda é o caminho a seguir para evitar criado dinamicamente atributos.

  2. Usando slots de vir também com o seu próprio custo.Ele vai quebrar a serialização (e.g.picles).Ele também vai quebrar a herança múltipla.Uma classe não pode herdar de mais de uma classe que define slots ou chapéu de uma instância layout definido no código C (como lista, tupla ou int).

A maneira correta é substituir __setattr__. É para isso que está lá.

Gosto muito da solução que usa um decorador, porque é fácil usá -lo para muitas classes em um projeto, com adições mínimas para cada classe. Mas não funciona bem com a herança. Então, aqui está a minha versão: ele substitui apenas a função __setattr__ - se o atributo não existir e a função do chamador não for __init__, ele imprime uma mensagem de erro.

import inspect                                                                                                                             

def froze_it(cls):                                                                                                                      

    def frozensetattr(self, key, value):                                                                                                   
        if not hasattr(self, key) and inspect.stack()[1][3] != "__init__":                                                                 
            print("Class {} is frozen. Cannot set {} = {}"                                                                                 
                  .format(cls.__name__, key, value))                                                                                       
        else:                                                                                                                              
            self.__dict__[key] = value                                                                                                     

    cls.__setattr__ = frozensetattr                                                                                                        
    return cls                                                                                                                             

@froze_it                                                                                                                                  
class A:                                                                                                                                   
    def __init__(self):                                                                                                                    
        self._a = 0                                                                                                                        

a = A()                                                                                                                                    
a._a = 1                                                                                                                                   
a._b = 2 # error

Aqui está a abordagem que eu criei que não precisa de um atributo ou método _frozen para congelamento () no init.

Durante iniciar Acabei de adicionar todos os atributos de classe à instância.

Eu gosto disso porque não há _frozen, Freeze () e _frozen também não aparece na saída VARS (Instância).

class MetaModel(type):
    def __setattr__(self, name, value):
        raise AttributeError("Model classes do not accept arbitrary attributes")

class Model(object):
    __metaclass__ = MetaModel

    # init will take all CLASS attributes, and add them as SELF/INSTANCE attributes
    def __init__(self):
        for k, v in self.__class__.__dict__.iteritems():
            if not k.startswith("_"):
                self.__setattr__(k, v)

    # setattr, won't allow any attributes to be set on the SELF/INSTANCE that don't already exist
    def __setattr__(self, name, value):
        if not hasattr(self, name):
            raise AttributeError("Model instances do not accept arbitrary attributes")
        else:
            object.__setattr__(self, name, value)


# Example using            
class Dog(Model):
    name = ''
    kind = 'canine'

d, e = Dog(), Dog()
print vars(d)
print vars(e)
e.junk = 'stuff' # fails

Que tal isso:

class A():
    __allowed_attr=('_x', '_y')

    def __init__(self,x=0,y=0):
        self._x=x
        self._y=y

    def __setattr__(self,attribute,value):
        if not attribute in self.__class__.__allowed_attr:
            raise AttributeError
        else:
            super().__setattr__(attribute,value)

Eu gosto do "congelado" de Jochen Ritzel. O inconveniente é que o A variável isfrozen aparece então ao imprimir uma classe .__ dictEu segui esse problema dessa maneira, criando uma lista de atributos autorizados (semelhante a slots):

class Frozen(object):
    __List = []
    def __setattr__(self, key, value):
        setIsOK = False
        for item in self.__List:
            if key == item:
                setIsOK = True

        if setIsOK == True:
            object.__setattr__(self, key, value)
        else:
            raise TypeError( "%r has no attributes %r" % (self, key) )

class Test(Frozen):
    _Frozen__List = ["attr1","attr2"]
    def __init__(self):
        self.attr1   =  1
        self.attr2   =  1

o FrozenClass por Jochen Ritzel é legal, mas chamando _frozen() Ao inicializar uma aula toda vez que não é tão legal (e você precisa correr o risco de esquecê -la). Eu adicionei um __init_slots__ função:

class FrozenClass(object):
    __isfrozen = False
    def _freeze(self):
        self.__isfrozen = True
    def __init_slots__(self, slots):
        for key in slots:
            object.__setattr__(self, key, None)
        self._freeze()
    def __setattr__(self, key, value):
        if self.__isfrozen and not hasattr(self, key):
            raise TypeError( "%r is a frozen class" % self )
        object.__setattr__(self, key, value)
class Test(FrozenClass):
    def __init__(self):
        self.__init_slots__(["x", "y"])
        self.x = 42#
        self.y = 2**3


a,b = Test(), Test()
a.x = 10
b.z = 10 # fails

pystrict é um decorador instalável do Pypi Inspirado por esta pergunta StackOverflow que pode ser usada com classes para congelá -las. Há um exemplo para o ReadMe que mostra por que um decorador como esse é necessário, mesmo que você tenha MyPy e Pylint em seu projeto:

pip install pystrict

Em seguida, basta usar o Decorador @Strict:

from pystrict import strict

@strict
class Blah
  def __init__(self):
     self.attr = 1
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top