Prevent creating new attributes outside __init__

前端 未结 11 1477
迷失自我
迷失自我 2020-12-04 07:23

I want to be able to create a class (in Python) that once initialized with __init__, does not accept new attributes, but accepts modifications of existing attri

相关标签:
11条回答
  • 2020-12-04 07:30

    The FrozenClass by Jochen Ritzel is cool, but calling _frozen() when initialing a class every time is not so cool (and you need to take the risk of forgetting it). I added a __init_slots__ function:

    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
    
    0 讨论(0)
  • 2020-12-04 07:31

    What about this:

    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)
    
    0 讨论(0)
  • 2020-12-04 07:34

    The proper way is to override __setattr__. That's what it's there for.

    0 讨论(0)
  • 2020-12-04 07:35

    Actually, you don't want __setattr__, you want __slots__. Add __slots__ = ('foo', 'bar', 'baz') to the class body, and Python will make sure that there's only foo, bar and baz on any instance. But read the caveats the documentation lists!

    0 讨论(0)
  • 2020-12-04 07:38

    Slots is the way to go:

    The pythonic way is to use slots instead of playing around with the __setter__. While it may solves the problem it does not give any performance improvement. The attributes of objects are stored in a dictionary "__dict__", this is the reason, why you can dynamically add attributes to objects of classes that we have created so far. Using a dictionary for attribute storage is very convenient, but it can mean a waste of space for objects, which have only a small amount of instance variables.

    Slots are a nice way to work around this space consumption problem. Instead of having a dynamic dict that allows adding attributes to objects dynamically, slots provide a static structure which prohibits additions after the creation of an instance.

    When we design a class, we can use slots to prevent the dynamic creation of attributes. To define slots, you have to define a list with the name __slots__. The list has to contain all the attributes, you want to use. We demonstrate this in the following class, in which the slots list contains only the name for an attribute "val".

    class S(object):
    
        __slots__ = ['val']
    
        def __init__(self, v):
            self.val = v
    
    
    x = S(42)
    print(x.val)
    
    x.new = "not possible"
    

    => It fails to create an attribute "new":

    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. Since Python 3.3 the advantage optimizing the space consumption is not as impressive any more. With Python 3.3 Key-Sharing Dictionaries are used for the storage of objects. The attributes of the instances are capable of sharing part of their internal storage between each other, i.e. the part which stores the keys and their corresponding hashes. This helps to reduce the memory consumption of programs, which create many instances of non-builtin types. But still is the way to go to avoid dynamically created attributes.

    2. Using slots come also with it's own cost. It will break serialization (e.g. pickle). It will also break multiple inheritance. A class can't inherit from more than one class that either defines slots or hat an instance layout defined in C code (like list, tuple or int).

    0 讨论(0)
  • 2020-12-04 07:38

    I like very much the solution that uses a decorator, because it's easy to use it for many classes across a project, with minimum additions for each class. But it doesn't work well with inheritance. So here is my version: It only overrides the __setattr__ function - if the attribute doesn't exist and the caller function is not __init__, it prints an error message.

    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
    
    0 讨论(0)
提交回复
热议问题