Prevent creating new attributes outside __init__

前端 未结 11 1478
迷失自我
迷失自我 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:40

    If someone is interested in doing that with a decorator, here is a working solution:

    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
    

    Pretty straightforward to use:

    @froze_it 
    class Foo(object):
        def __init__(self):
            self.bar = 10
    
    foo = Foo()
    foo.bar = 42
    foo.foobar = "no way"
    

    Result:

    >>> Class Foo is frozen. Cannot set foobar = no way
    
    0 讨论(0)
  • 2020-12-04 07:43

    I wouldn't use __dict__ directly, but you can add a function to explicitly "freeze" a instance:

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

    pystrict is a pypi installable decorator inspired by this stackoverflow question that can be used with classes to freeze them. There is an example to the README that shows why a decorator like this is needed even if you have mypy and pylint running on your project:

    pip install pystrict

    Then just use the @strict decorator:

    from pystrict import strict
    
    @strict
    class Blah
      def __init__(self):
         self.attr = 1
    
    0 讨论(0)
  • 2020-12-04 07:48

    Here is approach i came up with that doesn't need a _frozen attribute or method to freeze() in init.

    During init i just add all class attributes to the instance.

    I like this because there is no _frozen, freeze(), and _frozen also does not show up in the vars(instance) output.

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

    I like the "Frozen" of Jochen Ritzel. The inconvenient is that the isfrozen variable then appears when printing a Class.__dict I went around this problem this way by creating a list of authorized attributes (similar to 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
    
    0 讨论(0)
提交回复
热议问题