Thin proxy class for hashables - python

I need some kind of thin wrapper object to mark dictionary keys, like:
d = {
Required('name'): str,
Optional('age'): int,
}
And these wrappers should behave like the wrapped object (comparison, hashing, etc):
marked = Required('name')
marked == 'name' #-> True
d[marked] = 'hello'
d['name'] #-> 'hello'
With a single additional property: it should remember the added class:
isinstance(marked, Required) #-> True
and the added class should have custom methods.
This is actually something like a mixin on hashable objects.
I dislike those heavyweight Proxy Patterns which mimic all special properties and thinking of the following idea instead:
class Wrapper(object):
def __new__(cls, value):
value_type = type(value)
Type = type(
value_type.__name__, # same name
(cls, value_type), # Wrapper + bases
{})
Type.__new__ = value_type.__new__ # prevent recursion
return Type(value)
# Override method
def __repr__(self):
return 'Wrapper({})'.format(self)
Better ideas?

Your Wrapper class will work, yes. However, if all you wanted is a wrapper to act as a substitute key with extra methods, then I'd just create a dedicated class instead. Explicit is better than implicit here.
This class only has to proxy the __eq__ and __hash__ methods:
class HashableProxy(object):
def __init__(self, wrapped):
self._wrapped = wrapped
def __eq__(self, other):
return self._wrapped == other
def __hash__(self):
return hash(self._wrapped)
class Required(HashableProxy):
pass
class Optional(HashableProxy):
pass
You can add methods to this as required.
Demo:
>>> marked = Required('name')
>>> marked == 'name'
True
>>> d = {}
>>> d[marked] = 'hello'
>>> d['name']
'hello'
>>> isinstance(marked, Required)
True

Related

Why does setattr not work on spacy token? [duplicate]

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 attributes. There's several hack-ish ways I can see to do this, for example having a __setattr__ method such as
def __setattr__(self, attribute, value):
if not attribute in self.__dict__:
print "Cannot set %s" % attribute
else:
self.__dict__[attribute] = value
and then editing __dict__ directly inside __init__, but I was wondering if there is a 'proper' way to do this?
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
Slots is the way to go:
The pythonic way is to use slots instead of playing around with the __setter__. While it may solve 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'
Notes:
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.
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 has an instance layout defined in C code (like list, tuple or int).
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
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!
The proper way is to override __setattr__. That's what it's there for.
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
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)
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
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
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
None of the answers mention the performance impact of overriding __setattr__, which can be an issue when creating many small objects. (And __slots__ would be the performant solution but limits pickle/inheritance).
So I came up with this variant which installs our slower settatr after init:
class FrozenClass:
def freeze(self):
def frozen_setattr(self, key, value):
if not hasattr(self, key):
raise TypeError("Cannot set {}: {} is a frozen class".format(key, self))
object.__setattr__(self, key, value)
self.__setattr__ = frozen_setattr
class Foo(FrozenClass): ...
If you don't want to call freeze at the end of __init__, if inheritance is an issue, or if you don't want it in vars(), it can also be adapted: for example here is a decorator version based on the pystrict answer:
import functools
def strict(cls):
cls._x_setter = getattr(cls, "__setattr__", object.__setattr__)
cls._x_init = cls.__init__
#functools.wraps(cls.__init__)
def wrapper(self, *args, **kwargs):
cls._x_init(self, *args, **kwargs)
def frozen_setattr(self, key, value):
if not hasattr(self, key):
raise TypeError("Class %s is frozen. Cannot set '%s'." % (cls.__name__, key))
cls._x_setter(self, key, value)
cls.__setattr__ = frozen_setattr
cls.__init__ = wrapper
return cls
#strict
class Foo: ...
I wrote pystrict as a solution to this problem. It's too large to paste all of the code in stackoverflow.
pystrict is a pypi installable decorator that can be used with classes to freeze them. Many solutions here don't properly support inheritance.
If __slots__ doesn't work for you (because of inheritance issues), this is a good alternative.
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
#dataclass(slots=True) Nirvana (Python 3.10)
I'm in love with this #dataclass thing:
main.py
from dataclasses import dataclass
#dataclass(slots=True)
class C:
n: int
s: str
c = C(n=1, s='one')
assert c.n == 1
assert c.s == 'one'
c.n == 2
c.s == 'two'
c.asdf = 2
Outcome:
Traceback (most recent call last):
File "/home/ciro/main.py", line 15, in <module>
c.asdf = 2
AttributeError: 'C' object has no attribute 'asdf'
Note how #dataclass only requires use to define our attributes once with type annotations
n: int
s: str
and then, without any repetition we get for free:
def __init__(n, s):
self.n = n
self.s = s
__slots__ = ['n', 's']
Other free things not shown in this example:
__str__
__eq__: Compare object instances for equality by their attributes
__hash__ if you also use frozen=True: Object of custom type as dictionary key
Tested on Python 3.10.7, Ubuntu 22.10.

How to get all values of variables in class?

class NiceClass():
some_value = SomeObject(...)
some_other_value = SomeOtherObject(...)
#classmethod
def get_all_vars(cls):
...
I want get_all_vars() to return [SomeObject(...), SomeOtherObject(...)], or more specifically, the values of the variables in cls.
Solutions tried that didn't work out for me:
return [cls.some_value, cls.some_other_value, ...] (requires listing the variable manually)
subclassing Enum then using list(cls) (requires using some_value.value to access the value elsewhere in the program, also type hinting would be a mess)
namedtuples (nope not touching that subject, heard it was much more complicated than Enum)
[value for key, value in vars(cls).items() if not callable(value) and not key.startswith("__")] (too hacky due to using vars(cls), also for some reason it also includes get_all_vars due to it being a classmethod)
There are two ways. This is a straight answer to your question:
class Foo:
pass
class Bar:
x: int = 1
y: str = 'hello'
z: Foo = Foo()
#classmethod
def get_all(cls):
xs = []
for name, value in vars(cls).items():
if not (name.startswith('__') or isinstance(value, classmethod)):
xs.append(value)
return xs
This is what I suggest:
from dataclasses import dataclass, fields
class Foo:
pass
#dataclass
class Bar:
x: int = 1
y: str = 'hello'
z: Foo = Foo()
#classmethod
def get_defaults(cls):
return [f.default for f in fields(cls)]
#classmethod
def get_all(cls):
return [getattr(cls, f.name) for f in fields(cls)]
results:
Bar.get_defaults() == Bar.get_all()
# True -> [1, 'hello', __main__.Foo]
Bar.x = 10
Bar.get_defaults() == Bar.get_all()
# False -> [1, 'hello', __main__.Foo] != [10, 'hello', __main__.Foo]
You can create a list of values and define individual attributes at the same time with minimal boilerplate.
class NiceClass():
(some_value,
some_other_value,
) = _all_values = [
SomeObject(...),
SomeOtherObject(...)
]
#classmethod
def get_all_vars(cls):
return cls._all_values
The most obvious drawback to this is that it's easy to get the order of names and values out of sync.
Ideally, you might like to do something like
class NiceClass:
_attributes = {
'some_value': SomeObject(...),
'some_other_value': SomeOtherObject(...)
}
#classmethod
def get_all_vars(cls):
return cls._attributes.values()
and have some way to "inject" the contents of _attributes into the class namespace directly. The simplest way to do this is with a mix-in class that defines __init_subclass__:
class AddAttributes:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls.__dict__.update(cls._attributes)
class NiceClass(AddAttributes):
# As above
...
This might sound like a https://xyproblem.info/ but my solution might work in the other case as well. You can get the fields of an object by using __dict__ or vars (which is considered more pythonic given: Python dictionary from an object's fields)
You could do something like:
class ClassTest:
def __init__(self):
self.one = 1
self.two = 2
list(vars(ClassTest()).values())
But you will see that it has some limitations. It doesn't recognize the not in self.variable_name defined variables like you have used above. It might help you nonetheless, because you can simply define them in init.

How to prevent others from adding new attributes to an object / class in Python? [duplicate]

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 attributes. There's several hack-ish ways I can see to do this, for example having a __setattr__ method such as
def __setattr__(self, attribute, value):
if not attribute in self.__dict__:
print "Cannot set %s" % attribute
else:
self.__dict__[attribute] = value
and then editing __dict__ directly inside __init__, but I was wondering if there is a 'proper' way to do this?
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
Slots is the way to go:
The pythonic way is to use slots instead of playing around with the __setter__. While it may solve 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'
Notes:
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.
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 has an instance layout defined in C code (like list, tuple or int).
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
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!
The proper way is to override __setattr__. That's what it's there for.
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
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)
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
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
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
None of the answers mention the performance impact of overriding __setattr__, which can be an issue when creating many small objects. (And __slots__ would be the performant solution but limits pickle/inheritance).
So I came up with this variant which installs our slower settatr after init:
class FrozenClass:
def freeze(self):
def frozen_setattr(self, key, value):
if not hasattr(self, key):
raise TypeError("Cannot set {}: {} is a frozen class".format(key, self))
object.__setattr__(self, key, value)
self.__setattr__ = frozen_setattr
class Foo(FrozenClass): ...
If you don't want to call freeze at the end of __init__, if inheritance is an issue, or if you don't want it in vars(), it can also be adapted: for example here is a decorator version based on the pystrict answer:
import functools
def strict(cls):
cls._x_setter = getattr(cls, "__setattr__", object.__setattr__)
cls._x_init = cls.__init__
#functools.wraps(cls.__init__)
def wrapper(self, *args, **kwargs):
cls._x_init(self, *args, **kwargs)
def frozen_setattr(self, key, value):
if not hasattr(self, key):
raise TypeError("Class %s is frozen. Cannot set '%s'." % (cls.__name__, key))
cls._x_setter(self, key, value)
cls.__setattr__ = frozen_setattr
cls.__init__ = wrapper
return cls
#strict
class Foo: ...
I wrote pystrict as a solution to this problem. It's too large to paste all of the code in stackoverflow.
pystrict is a pypi installable decorator that can be used with classes to freeze them. Many solutions here don't properly support inheritance.
If __slots__ doesn't work for you (because of inheritance issues), this is a good alternative.
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
#dataclass(slots=True) Nirvana (Python 3.10)
I'm in love with this #dataclass thing:
main.py
from dataclasses import dataclass
#dataclass(slots=True)
class C:
n: int
s: str
c = C(n=1, s='one')
assert c.n == 1
assert c.s == 'one'
c.n == 2
c.s == 'two'
c.asdf = 2
Outcome:
Traceback (most recent call last):
File "/home/ciro/main.py", line 15, in <module>
c.asdf = 2
AttributeError: 'C' object has no attribute 'asdf'
Note how #dataclass only requires use to define our attributes once with type annotations
n: int
s: str
and then, without any repetition we get for free:
def __init__(n, s):
self.n = n
self.s = s
__slots__ = ['n', 's']
Other free things not shown in this example:
__str__
__eq__: Compare object instances for equality by their attributes
__hash__ if you also use frozen=True: Object of custom type as dictionary key
Tested on Python 3.10.7, Ubuntu 22.10.

How to make an immutable object in Python?

Although I have never needed this, it just struck me that making an immutable object in Python could be slightly tricky. You can't just override __setattr__, because then you can't even set attributes in the __init__. Subclassing a tuple is a trick that works:
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
But then you have access to the a and b variables through self[0] and self[1], which is annoying.
Is this possible in Pure Python? If not, how would I do it with a C extension?
(Answers that work only in Python 3 are acceptable).
Update:
As of Python 3.7, the way to go is to use the #dataclass decorator, see the newly accepted answer.
Yet another solution I just thought of: The simplest way to get the same behaviour as your original code is
Immutable = collections.namedtuple("Immutable", ["a", "b"])
It does not solve the problem that attributes can be accessed via [0] etc., but at least it's considerably shorter and provides the additional advantage of being compatible with pickle and copy.
namedtuple creates a type similar to what I described in this answer, i.e. derived from tuple and using __slots__. It is available in Python 2.6 or above.
The easiest way to do this is using __slots__:
class A(object):
__slots__ = []
Instances of A are immutable now, since you can't set any attributes on them.
If you want the class instances to contain data, you can combine this with deriving from tuple:
from operator import itemgetter
class Point(tuple):
__slots__ = []
def __new__(cls, x, y):
return tuple.__new__(cls, (x, y))
x = property(itemgetter(0))
y = property(itemgetter(1))
p = Point(2, 3)
p.x
# 2
p.y
# 3
Edit: If you want to get rid of indexing either, you can override __getitem__():
class Point(tuple):
__slots__ = []
def __new__(cls, x, y):
return tuple.__new__(cls, (x, y))
#property
def x(self):
return tuple.__getitem__(self, 0)
#property
def y(self):
return tuple.__getitem__(self, 1)
def __getitem__(self, item):
raise TypeError
Note that you can't use operator.itemgetter for the properties in thise case, since this would rely on Point.__getitem__() instead of tuple.__getitem__(). Fuerthermore this won't prevent the use of tuple.__getitem__(p, 0), but I can hardly imagine how this should constitute a problem.
I don't think the "right" way of creating an immutable object is writing a C extension. Python usually relies on library implementers and library users being consenting adults, and instead of really enforcing an interface, the interface should be clearly stated in the documentation. This is why I don't consider the possibility of circumventing an overridden __setattr__() by calling object.__setattr__() a problem. If someone does this, it's on her own risk.
Using a Frozen Dataclass
For Python 3.7+ you can use a Data Class with a frozen=True option, which is a very pythonic and maintainable way to do what you want.
It would look something like that:
from dataclasses import dataclass
#dataclass(frozen=True)
class Immutable:
a: Any
b: Any
As type hinting is required for dataclasses' fields, I have used Any from the typing module.
Reasons NOT to use a Namedtuple
Before Python 3.7 it was frequent to see namedtuples being used as immutable objects. It can be tricky in many ways, one of them is that the __eq__ method between namedtuples does not consider the objects' classes. For example:
from collections import namedtuple
ImmutableTuple = namedtuple("ImmutableTuple", ["a", "b"])
ImmutableTuple2 = namedtuple("ImmutableTuple2", ["a", "c"])
obj1 = ImmutableTuple(a=1, b=2)
obj2 = ImmutableTuple2(a=1, c=2)
obj1 == obj2 # will be True
As you see, even if the types of obj1 and obj2 are different, even if their fields' names are different, obj1 == obj2 still gives True. That's because the __eq__ method used is the tuple's one, which compares only the values of the fields given their positions. That can be a huge source of errors, specially if you are subclassing these classes.
..howto do it "properly" in C..
You could use Cython to create an extension type for Python:
cdef class Immutable:
cdef readonly object a, b
cdef object __weakref__ # enable weak referencing support
def __init__(self, a, b):
self.a, self.b = a, b
It works both Python 2.x and 3.
Tests
# compile on-the-fly
import pyximport; pyximport.install() # $ pip install cython
from immutable import Immutable
o = Immutable(1, 2)
assert o.a == 1, str(o.a)
assert o.b == 2
try: o.a = 3
except AttributeError:
pass
else:
assert 0, 'attribute must be readonly'
try: o[1]
except TypeError:
pass
else:
assert 0, 'indexing must not be supported'
try: o.c = 1
except AttributeError:
pass
else:
assert 0, 'no new attributes are allowed'
o = Immutable('a', [])
assert o.a == 'a'
assert o.b == []
o.b.append(3) # attribute may contain mutable object
assert o.b == [3]
try: o.c
except AttributeError:
pass
else:
assert 0, 'no c attribute'
o = Immutable(b=3,a=1)
assert o.a == 1 and o.b == 3
try: del o.b
except AttributeError:
pass
else:
assert 0, "can't delete attribute"
d = dict(b=3, a=1)
o = Immutable(**d)
assert o.a == d['a'] and o.b == d['b']
o = Immutable(1,b=3)
assert o.a == 1 and o.b == 3
try: object.__setattr__(o, 'a', 1)
except AttributeError:
pass
else:
assert 0, 'attributes are readonly'
try: object.__setattr__(o, 'c', 1)
except AttributeError:
pass
else:
assert 0, 'no new attributes'
try: Immutable(1,c=3)
except TypeError:
pass
else:
assert 0, 'accept only a,b keywords'
for kwd in [dict(a=1), dict(b=2)]:
try: Immutable(**kwd)
except TypeError:
pass
else:
assert 0, 'Immutable requires exactly 2 arguments'
If you don't mind indexing support then collections.namedtuple suggested by #Sven Marnach is preferrable:
Immutable = collections.namedtuple("Immutable", "a b")
Another idea would be to completely disallow __setattr__ and use object.__setattr__ in the constructor:
class Point(object):
def __init__(self, x, y):
object.__setattr__(self, "x", x)
object.__setattr__(self, "y", y)
def __setattr__(self, *args):
raise TypeError
def __delattr__(self, *args):
raise TypeError
Of course you could use object.__setattr__(p, "x", 3) to modify a Point instance p, but your original implementation suffers from the same problem (try tuple.__setattr__(i, "x", 42) on an Immutable instance).
You can apply the same trick in your original implementation: get rid of __getitem__(), and use tuple.__getitem__() in your property functions.
You could create a #immutable decorator that either overrides the __setattr__ and change the __slots__ to an empty list, then decorate the __init__ method with it.
Edit: As the OP noted, changing the __slots__ attribute only prevents the creation of new attributes, not the modification.
Edit2: Here's an implementation:
Edit3: Using __slots__ breaks this code, because if stops the creation of the object's __dict__. I'm looking for an alternative.
Edit4: Well, that's it. It's a but hackish, but works as an exercise :-)
class immutable(object):
def __init__(self, immutable_params):
self.immutable_params = immutable_params
def __call__(self, new):
params = self.immutable_params
def __set_if_unset__(self, name, value):
if name in self.__dict__:
raise Exception("Attribute %s has already been set" % name)
if not name in params:
raise Exception("Cannot create atribute %s" % name)
self.__dict__[name] = value;
def __new__(cls, *args, **kws):
cls.__setattr__ = __set_if_unset__
return super(cls.__class__, cls).__new__(cls, *args, **kws)
return __new__
class Point(object):
#immutable(['x', 'y'])
def __new__(): pass
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
p.x = 3 # Exception: Attribute x has already been set
p.z = 4 # Exception: Cannot create atribute z
I don't think it is entirely possible except by using either a tuple or a namedtuple. No matter what, if you override __setattr__() the user can always bypass it by calling object.__setattr__() directly. Any solution that depends on __setattr__ is guaranteed not to work.
The following is about the nearest you can get without using some sort of tuple:
class Immutable:
__slots__ = ['a', 'b']
def __init__(self, a, b):
object.__setattr__(self, 'a', a)
object.__setattr__(self, 'b', b)
def __setattr__(self, *ignored):
raise NotImplementedError
__delattr__ = __setattr__
but it breaks if you try hard enough:
>>> t = Immutable(1, 2)
>>> t.a
1
>>> object.__setattr__(t, 'a', 2)
>>> t.a
2
but Sven's use of namedtuple is genuinely immutable.
Update
Since the question has been updated to ask how to do it properly in C, here's my answer on how to do it properly in Cython:
First immutable.pyx:
cdef class Immutable:
cdef object _a, _b
def __init__(self, a, b):
self._a = a
self._b = b
property a:
def __get__(self):
return self._a
property b:
def __get__(self):
return self._b
def __repr__(self):
return "<Immutable {0}, {1}>".format(self.a, self.b)
and a setup.py to compile it (using the command setup.py build_ext --inplace:
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules = [Extension("immutable", ["immutable.pyx"])]
setup(
name = 'Immutable object',
cmdclass = {'build_ext': build_ext},
ext_modules = ext_modules
)
Then to try it out:
>>> from immutable import Immutable
>>> p = Immutable(2, 3)
>>> p
<Immutable 2, 3>
>>> p.a = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: attribute 'a' of 'immutable.Immutable' objects is not writable
>>> object.__setattr__(p, 'a', 1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: attribute 'a' of 'immutable.Immutable' objects is not writable
>>> p.a, p.b
(2, 3)
>>>
I've made immutable classes by overriding __setattr__, and allowing the set if the caller is __init__:
import inspect
class Immutable(object):
def __setattr__(self, name, value):
if inspect.stack()[2][3] != "__init__":
raise Exception("Can't mutate an Immutable: self.%s = %r" % (name, value))
object.__setattr__(self, name, value)
This isn't quite enough yet, since it allows anyone's ___init__ to change the object, but you get the idea.
Here's an elegant solution:
class Immutable(object):
def __setattr__(self, key, value):
if not hasattr(self, key):
super().__setattr__(key, value)
else:
raise RuntimeError("Can't modify immutable object's attribute: {}".format(key))
Inherit from this class, initialize your fields in the constructor, and you'e all set.
In addition to the excellent other answers I like to add a method for python 3.4 (or maybe 3.3). This answer builds upon several previouse answers to this question.
In python 3.4, you can use properties without setters to create class members that cannot be modified. (In earlier versions assigning to properties without a setter was possible.)
class A:
__slots__=['_A__a']
def __init__(self, aValue):
self.__a=aValue
#property
def a(self):
return self.__a
You can use it like this:
instance=A("constant")
print (instance.a)
which will print "constant"
But calling instance.a=10 will cause:
AttributeError: can't set attribute
Explaination: properties without setters are a very recent feature of python 3.4 (and I think 3.3). If you try to assign to such a property, an Error will be raised.
Using slots I restrict the membervariables to __A_a (which is __a).
Problem: Assigning to _A__a is still possible (instance._A__a=2). But if you assign to a private variable, it is your own fault...
This answer among others, however, discourages the use of __slots__. Using other ways to prevent attribute creation might be preferrable.
So, I am writing respective of python 3:
I) with the help of data class decorator and set frozen=True.
we can create immutable objects in python.
for this need to import data class from data classes lib and needs to set frozen=True
ex.
from dataclasses import dataclass
#dataclass(frozen=True)
class Location:
name: str
longitude: float = 0.0
latitude: float = 0.0
o/p:
>>> l = Location("Delhi", 112.345, 234.788)
>>> l.name
'Delhi'
>>> l.longitude
112.345
>>> l.latitude
234.788
>>> l.name = "Kolkata"
dataclasses.FrozenInstanceError: cannot assign to field 'name'
>>>
Source: https://realpython.com/python-data-classes/
If you are interested in objects with behavior, then namedtuple is almost your solution.
As described at the bottom of the namedtuple documentation, you can derive your own class from namedtuple; and then, you can add the behavior you want.
For example (code taken directly from the documentation):
class Point(namedtuple('Point', 'x y')):
__slots__ = ()
#property
def hypot(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
def __str__(self):
return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, self.hypot)
for p in Point(3, 4), Point(14, 5/7):
print(p)
This will result in:
Point: x= 3.000 y= 4.000 hypot= 5.000
Point: x=14.000 y= 0.714 hypot=14.018
This approach works for both Python 3 and Python 2.7 (tested on IronPython as well).
The only downside is that the inheritance tree is a bit weird; but this is not something you usually play with.
As of Python 3.7, you can use the #dataclass decorator in your class and it will be immutable like a struct! Though, it may or may not add a __hash__() method to your class. Quote:
hash() is used by built-in hash(), and when objects are added to hashed collections such as dictionaries and sets. Having a hash() implies that instances of the class are immutable. Mutability is a complicated property that depends on the programmer’s intent, the existence and behavior of eq(), and the values of the eq and frozen flags in the dataclass() decorator.
By default, dataclass() will not implicitly add a hash() method unless it is safe to do so. Neither will it add or change an existing explicitly defined hash() method. Setting the class attribute hash = None has a specific meaning to Python, as described in the hash() documentation.
If hash() is not explicit defined, or if it is set to None, then dataclass() may add an implicit hash() method. Although not recommended, you can force dataclass() to create a hash() method with unsafe_hash=True. This might be the case if your class is logically immutable but can nonetheless be mutated. This is a specialized use case and should be considered carefully.
Here the example from the docs linked above:
#dataclass
class InventoryItem:
'''Class for keeping track of an item in inventory.'''
name: str
unit_price: float
quantity_on_hand: int = 0
def total_cost(self) -> float:
return self.unit_price * self.quantity_on_hand
Just Like a dict
I have an open source library where I'm doing things in a functional way so moving data around in an immutable object is helpful. However, I don't want to have to transform my data object for the client to interact with them. So, I came up with this - it gives you a dict like object thats immutable + some helper methods.
Credit to Sven Marnach in his answer for the basic implementation of restricting property updating and deleting.
import json
# ^^ optional - If you don't care if it prints like a dict
# then rip this and __str__ and __repr__ out
class Immutable(object):
def __init__(self, **kwargs):
"""Sets all values once given
whatever is passed in kwargs
"""
for k,v in kwargs.items():
object.__setattr__(self, k, v)
def __setattr__(self, *args):
"""Disables setting attributes via
item.prop = val or item['prop'] = val
"""
raise TypeError('Immutable objects cannot have properties set after init')
def __delattr__(self, *args):
"""Disables deleting properties"""
raise TypeError('Immutable objects cannot have properties deleted')
def __getitem__(self, item):
"""Allows for dict like access of properties
val = item['prop']
"""
return self.__dict__[item]
def __repr__(self):
"""Print to repl in a dict like fashion"""
return self.pprint()
def __str__(self):
"""Convert to a str in a dict like fashion"""
return self.pprint()
def __eq__(self, other):
"""Supports equality operator
immutable({'a': 2}) == immutable({'a': 2})"""
if other is None:
return False
return self.dict() == other.dict()
def keys(self):
"""Paired with __getitem__ supports **unpacking
new = { **item, **other }
"""
return self.__dict__.keys()
def get(self, *args, **kwargs):
"""Allows for dict like property access
item.get('prop')
"""
return self.__dict__.get(*args, **kwargs)
def pprint(self):
"""Helper method used for printing that
formats in a dict like way
"""
return json.dumps(self,
default=lambda o: o.__dict__,
sort_keys=True,
indent=4)
def dict(self):
"""Helper method for getting the raw dict value
of the immutable object"""
return self.__dict__
Helper methods
def update(obj, **kwargs):
"""Returns a new instance of the given object with
all key/val in kwargs set on it
"""
return immutable({
**obj,
**kwargs
})
def immutable(obj):
return Immutable(**obj)
Examples
obj = immutable({
'alpha': 1,
'beta': 2,
'dalet': 4
})
obj.alpha # 1
obj['alpha'] # 1
obj.get('beta') # 2
del obj['alpha'] # TypeError
obj.alpha = 2 # TypeError
new_obj = update(obj, alpha=10)
new_obj is not obj # True
new_obj.get('alpha') == 10 # True
This way doesn't stop object.__setattr__ from working, but I've still found it useful:
class A(object):
def __new__(cls, children, *args, **kwargs):
self = super(A, cls).__new__(cls)
self._frozen = False # allow mutation from here to end of __init__
# other stuff you need to do in __new__ goes here
return self
def __init__(self, *args, **kwargs):
super(A, self).__init__()
self._frozen = True # prevent future mutation
def __setattr__(self, name, value):
# need to special case setting _frozen.
if name != '_frozen' and self._frozen:
raise TypeError('Instances are immutable.')
else:
super(A, self).__setattr__(name, value)
def __delattr__(self, name):
if self._frozen:
raise TypeError('Instances are immutable.')
else:
super(A, self).__delattr__(name)
you may need to override more stuff (like __setitem__) depending on the use case.
Classes which inherit from the following Immutable class are immutable, as are their instances, after their __init__ method finishes executing. Since it's pure python, as others have pointed out, there's nothing stopping someone from using the mutating special methods from the base object and type, but this is enough to stop anyone from mutating a class/instance by accident.
It works by hijacking the class-creation process with a metaclass.
"""Subclasses of class Immutable are immutable after their __init__ has run, in
the sense that all special methods with mutation semantics (in-place operators,
setattr, etc.) are forbidden.
"""
# Enumerate the mutating special methods
mutation_methods = set()
# Arithmetic methods with in-place operations
iarithmetic = '''add sub mul div mod divmod pow neg pos abs bool invert lshift
rshift and xor or floordiv truediv matmul'''.split()
for op in iarithmetic:
mutation_methods.add('__i%s__' % op)
# Operations on instance components (attributes, items, slices)
for verb in ['set', 'del']:
for component in '''attr item slice'''.split():
mutation_methods.add('__%s%s__' % (verb, component))
# Operations on properties
mutation_methods.update(['__set__', '__delete__'])
def checked_call(_self, name, method, *args, **kwargs):
"""Calls special method method(*args, **kw) on self if mutable."""
self = args[0] if isinstance(_self, object) else _self
if not getattr(self, '__mutable__', True):
# self told us it's immutable, so raise an error
cname= (self if isinstance(self, type) else self.__class__).__name__
raise TypeError('%s is immutable, %s disallowed' % (cname, name))
return method(*args, **kwargs)
def method_wrapper(_self, name):
"Wrap a special method to check for mutability."
method = getattr(_self, name)
def wrapper(*args, **kwargs):
return checked_call(_self, name, method, *args, **kwargs)
wrapper.__name__ = name
wrapper.__doc__ = method.__doc__
return wrapper
def wrap_mutating_methods(_self):
"Place the wrapper methods on mutative special methods of _self"
for name in mutation_methods:
if hasattr(_self, name):
method = method_wrapper(_self, name)
type.__setattr__(_self, name, method)
def set_mutability(self, ismutable):
"Set __mutable__ by using the unprotected __setattr__"
b = _MetaImmutable if isinstance(self, type) else Immutable
super(b, self).__setattr__('__mutable__', ismutable)
class _MetaImmutable(type):
'''The metaclass of Immutable. Wraps __init__ methods via __call__.'''
def __init__(cls, *args, **kwargs):
# Make class mutable for wrapping special methods
set_mutability(cls, True)
wrap_mutating_methods(cls)
# Disable mutability
set_mutability(cls, False)
def __call__(cls, *args, **kwargs):
'''Make an immutable instance of cls'''
self = cls.__new__(cls)
# Make the instance mutable for initialization
set_mutability(self, True)
# Execute cls's custom initialization on this instance
self.__init__(*args, **kwargs)
# Disable mutability
set_mutability(self, False)
return self
# Given a class T(metaclass=_MetaImmutable), mutative special methods which
# already exist on _MetaImmutable (a basic type) cannot be over-ridden
# programmatically during _MetaImmutable's instantiation of T, because the
# first place python looks for a method on an object is on the object's
# __class__, and T.__class__ is _MetaImmutable. The two extant special
# methods on a basic type are __setattr__ and __delattr__, so those have to
# be explicitly overridden here.
def __setattr__(cls, name, value):
checked_call(cls, '__setattr__', type.__setattr__, cls, name, value)
def __delattr__(cls, name, value):
checked_call(cls, '__delattr__', type.__delattr__, cls, name, value)
class Immutable(object):
"""Inherit from this class to make an immutable object.
__init__ methods of subclasses are executed by _MetaImmutable.__call__,
which enables mutability for the duration.
"""
__metaclass__ = _MetaImmutable
class T(int, Immutable): # Checks it works with multiple inheritance, too.
"Class for testing immutability semantics"
def __init__(self, b):
self.b = b
#classmethod
def class_mutation(cls):
cls.a = 5
def instance_mutation(self):
self.c = 1
def __iadd__(self, o):
pass
def not_so_special_mutation(self):
self +=1
def immutabilityTest(f, name):
"Call f, which should try to mutate class T or T instance."
try:
f()
except TypeError, e:
assert 'T is immutable, %s disallowed' % name in e.args
else:
raise RuntimeError('Immutability failed!')
immutabilityTest(T.class_mutation, '__setattr__')
immutabilityTest(T(6).instance_mutation, '__setattr__')
immutabilityTest(T(6).not_so_special_mutation, '__iadd__')
The third party attr module provides this functionality.
Edit: python 3.7 has adopted this idea into the stdlib with #dataclass.
$ pip install attrs
$ python
>>> #attr.s(frozen=True)
... class C(object):
... x = attr.ib()
>>> i = C(1)
>>> i.x = 2
Traceback (most recent call last):
...
attr.exceptions.FrozenInstanceError: can't set attribute
attr implements frozen classes by overriding __setattr__ and has a minor performance impact at each instantiation time, according to the documentation.
If you're in the habit of using classes as datatypes, attr may be especially useful as it takes care of the boilerplate for you (but doesn't do any magic). In particular, it writes nine dunder (__X__) methods for you (unless you turn any of them off), including repr, init, hash and all the comparison functions.
attr also provides a helper for __slots__.
You can override setattr and still use init to set the variable. You would use super class setattr. here is the code.
class Immutable:
__slots__ = ('a','b')
def __init__(self, a , b):
super().__setattr__('a',a)
super().__setattr__('b',b)
def __str__(self):
return "".format(self.a, self.b)
def __setattr__(self, *ignored):
raise NotImplementedError
def __delattr__(self, *ignored):
raise NotImplementedError
I found a way to do it without subclassing tuple, namedtuple etc. All you need to do is to disable setattr and delattr (and also setitem and delitem if you want to make a collection immutable) after the initiation:
def __init__(self, *args, **kwargs):
# something here
self.lock()
where lock can look like this:
#classmethod
def lock(cls):
def raiser(*a):
raise TypeError('this instance is immutable')
cls.__setattr__ = raiser
cls.__delattr__ = raiser
if hasattr(cls, '__setitem__'):
cls.__setitem__ = raiser
cls.__delitem__ = raiser
So you can create class Immutable with this method and use it the way I showed.
If you don't want to write self.lock() in every single init you can make it automatically with metaclasses:
class ImmutableType(type):
#classmethod
def change_init(mcs, original_init_method):
def __new_init__(self, *args, **kwargs):
if callable(original_init_method):
original_init_method(self, *args, **kwargs)
cls = self.__class__
def raiser(*a):
raise TypeError('this instance is immutable')
cls.__setattr__ = raiser
cls.__delattr__ = raiser
if hasattr(cls, '__setitem__'):
cls.__setitem__ = raiser
cls.__delitem__ = raiser
return __new_init__
def __new__(mcs, name, parents, kwargs):
kwargs['__init__'] = mcs.change_init(kwargs.get('__init__'))
return type.__new__(mcs, name, parents, kwargs)
class Immutable(metaclass=ImmutableType):
pass
Test
class SomeImmutableClass(Immutable):
def __init__(self, some_value: int):
self.important_attr = some_value
def some_method(self):
return 2 * self.important_attr
ins = SomeImmutableClass(3)
print(ins.some_method()) # 6
ins.important_attr += 1 # TypeError
ins.another_attr = 2 # TypeError
The basic solution below addresses the following scenario:
__init__() can be written accessing the attributes as usual.
AFTER that the OBJECT is frozen for attributes changes only:
The idea is to override __setattr__ method and replace its implementation each time the object frozen status is changed.
So we need some method (_freeze) which stores those two implementations and switches between them when requested.
This mechanism may be implemented inside the user class or inherited from a special Freezer class as shown below:
class Freezer:
def _freeze(self, do_freeze=True):
def raise_sa(*args):
raise AttributeError("Attributes are frozen and can not be changed!")
super().__setattr__('_active_setattr', (super().__setattr__, raise_sa)[do_freeze])
def __setattr__(self, key, value):
return self._active_setattr(key, value)
class A(Freezer):
def __init__(self):
self._freeze(False)
self.x = 10
self._freeze()
I needed this a little while ago and decided to make a Python package for it. The initial version is on PyPI now:
$ pip install immutable
To use:
>>> from immutable import ImmutableFactory
>>> MyImmutable = ImmutableFactory.create(prop1=1, prop2=2, prop3=3)
>>> MyImmutable.prop1
1
Full docs here: https://github.com/theengineear/immutable
Hope it helps, it wraps a namedtuple as has been discussed, but makes instantiation much simpler.
An alternative approach is to create a wrapper which makes an instance immutable.
class Immutable(object):
def __init__(self, wrapped):
super(Immutable, self).__init__()
object.__setattr__(self, '_wrapped', wrapped)
def __getattribute__(self, item):
return object.__getattribute__(self, '_wrapped').__getattribute__(item)
def __setattr__(self, key, value):
raise ImmutableError('Object {0} is immutable.'.format(self._wrapped))
__delattr__ = __setattr__
def __iter__(self):
return object.__getattribute__(self, '_wrapped').__iter__()
def next(self):
return object.__getattribute__(self, '_wrapped').next()
def __getitem__(self, item):
return object.__getattribute__(self, '_wrapped').__getitem__(item)
immutable_instance = Immutable(my_instance)
This is useful in situations where only some instances have to be immutable (like default arguments of function calls).
Can also be used in immutable factories like:
#classmethod
def immutable_factory(cls, *args, **kwargs):
return Immutable(cls.__init__(*args, **kwargs))
Also protects from object.__setattr__, but fallable to other tricks due to Python's dynamic nature.
I used the same idea as Alex: a meta-class and an "init marker", but in combination with over-writing __setattr__:
>>> from abc import ABCMeta
>>> _INIT_MARKER = '_#_in_init_#_'
>>> class _ImmutableMeta(ABCMeta):
...
... """Meta class to construct Immutable."""
...
... def __call__(cls, *args, **kwds):
... obj = cls.__new__(cls, *args, **kwds)
... object.__setattr__(obj, _INIT_MARKER, True)
... cls.__init__(obj, *args, **kwds)
... object.__delattr__(obj, _INIT_MARKER)
... return obj
...
>>> def _setattr(self, name, value):
... if hasattr(self, _INIT_MARKER):
... object.__setattr__(self, name, value)
... else:
... raise AttributeError("Instance of '%s' is immutable."
... % self.__class__.__name__)
...
>>> def _delattr(self, name):
... raise AttributeError("Instance of '%s' is immutable."
... % self.__class__.__name__)
...
>>> _im_dict = {
... '__doc__': "Mix-in class for immutable objects.",
... '__copy__': lambda self: self, # self is immutable, so just return it
... '__setattr__': _setattr,
... '__delattr__': _delattr}
...
>>> Immutable = _ImmutableMeta('Immutable', (), _im_dict)
Note: I'm calling the meta-class directly to make it work both for Python 2.x and 3.x.
>>> class T1(Immutable):
...
... def __init__(self, x=1, y=2):
... self.x = x
... self.y = y
...
>>> t1 = T1(y=8)
>>> t1.x, t1.y
(1, 8)
>>> t1.x = 7
AttributeError: Instance of 'T1' is immutable.
It does work also with slots ...:
>>> class T2(Immutable):
...
... __slots__ = 's1', 's2'
...
... def __init__(self, s1, s2):
... self.s1 = s1
... self.s2 = s2
...
>>> t2 = T2('abc', 'xyz')
>>> t2.s1, t2.s2
('abc', 'xyz')
>>> t2.s1 += 'd'
AttributeError: Instance of 'T2' is immutable.
... and multiple inheritance:
>>> class T3(T1, T2):
...
... def __init__(self, x, y, s1, s2):
... T1.__init__(self, x, y)
... T2.__init__(self, s1, s2)
...
>>> t3 = T3(12, 4, 'a', 'b')
>>> t3.x, t3.y, t3.s1, t3.s2
(12, 4, 'a', 'b')
>>> t3.y -= 3
AttributeError: Instance of 'T3' is immutable.
Note, however, that mutable attributes stay to be mutable:
>>> t3 = T3(12, [4, 7], 'a', 'b')
>>> t3.y.append(5)
>>> t3.y
[4, 7, 5]
One thing that's not really included here is total immutability... not just the parent object, but all the children as well. tuples/frozensets may be immutable for instance, but the objects that it's part of may not be. Here's a small (incomplete) version that does a decent job of enforcing immutability all the way down:
# Initialize lists
a = [1,2,3]
b = [4,5,6]
c = [7,8,9]
l = [a,b]
# We can reassign in a list
l[0] = c
# But not a tuple
t = (a,b)
#t[0] = c -> Throws exception
# But elements can be modified
t[0][1] = 4
t
([1, 4, 3], [4, 5, 6])
# Fix it back
t[0][1] = 2
li = ImmutableObject(l)
li
[[1, 2, 3], [4, 5, 6]]
# Can't assign
#li[0] = c will fail
# Can reference
li[0]
[1, 2, 3]
# But immutability conferred on returned object too
#li[0][1] = 4 will throw an exception
# Full solution should wrap all the comparison e.g. decorators.
# Also, you'd usually want to add a hash function, i didn't put
# an interface for that.
class ImmutableObject(object):
def __init__(self, inobj):
self._inited = False
self._inobj = inobj
self._inited = True
def __repr__(self):
return self._inobj.__repr__()
def __str__(self):
return self._inobj.__str__()
def __getitem__(self, key):
return ImmutableObject(self._inobj.__getitem__(key))
def __iter__(self):
return self._inobj.__iter__()
def __setitem__(self, key, value):
raise AttributeError, 'Object is read-only'
def __getattr__(self, key):
x = getattr(self._inobj, key)
if callable(x):
return x
else:
return ImmutableObject(x)
def __hash__(self):
return self._inobj.__hash__()
def __eq__(self, second):
return self._inobj.__eq__(second)
def __setattr__(self, attr, value):
if attr not in ['_inobj', '_inited'] and self._inited == True:
raise AttributeError, 'Object is read-only'
object.__setattr__(self, attr, value)
You can just override setAttr in the final statement of init. THen you can construct but not change. Obviously you can still override by usint object.setAttr but in practice most languages have some form of reflection so immutablility is always a leaky abstraction. Immutability is more about preventing clients from accidentally violating the contract of an object. I use:
=============================
The original solution offered was incorrect, this was updated based on the comments using the solution from here
The original solution is wrong in an interesting way, so it is included at the bottom.
===============================
class ImmutablePair(object):
__initialised = False # a class level variable that should always stay false.
def __init__(self, a, b):
try :
self.a = a
self.b = b
finally:
self.__initialised = True #an instance level variable
def __setattr__(self, key, value):
if self.__initialised:
self._raise_error()
else :
super(ImmutablePair, self).__setattr__(key, value)
def _raise_error(self, *args, **kw):
raise NotImplementedError("Attempted To Modify Immutable Object")
if __name__ == "__main__":
immutable_object = ImmutablePair(1,2)
print immutable_object.a
print immutable_object.b
try :
immutable_object.a = 3
except Exception as e:
print e
print immutable_object.a
print immutable_object.b
Output :
1
2
Attempted To Modify Immutable Object
1
2
======================================
Original Implementation:
It was pointed out in the comments, correctly, that this does not in fact work, as it prevents the creation of more than one object as you are overriding the class setattr method, which means a second cannot be created as self.a = will fail on the second initialisation.
class ImmutablePair(object):
def __init__(self, a, b):
self.a = a
self.b = b
ImmutablePair.__setattr__ = self._raise_error
def _raise_error(self, *args, **kw):
raise NotImplementedError("Attempted To Modify Immutable Object")
I've created a small class decorator decorator to make class immutable (except inside __init__). As part of https://github.com/google/etils.
from etils import epy
#epy.frozen
class A:
def __init__(self):
self.x = 123 # Inside `__init__`, attribute can be assigned
a = A()
a.x = 456 # AttributeError
This support inheritance too.
Implementation:
_Cls = TypeVar('_Cls')
def frozen(cls: _Cls) -> _Cls:
"""Class decorator which prevent mutating attributes after `__init__`."""
if not isinstance(cls, type):
raise TypeError(f'{cls.__name__} is not a class.')
cls.__init__ = _wrap_init(cls.__init__)
cls.__setattr__ = _wrap_setattr(cls.__setattr__)
return cls
def _wrap_init(init_fn):
"""`__init__` wrapper."""
#functools.wraps(init_fn)
def new_init(self, *args, **kwargs):
if hasattr(self, '_epy_is_init_done'):
# `_epy_is_init_done` already created, so it means we're
# a `super().__init__` call.
return init_fn(self, *args, **kwargs)
object.__setattr__(self, '_epy_is_init_done', False)
init_fn(self, *args, **kwargs)
object.__setattr__(self, '_epy_is_init_done', True)
return new_init
def _wrap_setattr(setattr_fn):
"""`__setattr__` wrapper."""
#functools.wraps(setattr_fn)
def new_setattr(self, name, value):
if not hasattr(self, '_epy_is_init_done'):
raise ValueError(
'Child of `#epy.frozen` class should be `#epy.frozen` too. (Error'
f' raised by {type(self)})'
)
if not self._epy_is_init_done: # pylint: disable=protected-access
return setattr_fn(self, name, value)
else:
raise AttributeError(
f'Cannot assign {name!r} in `#epy.frozen` class {type(self)}'
)
return new_setattr

Prevent creating new attributes outside __init__

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 attributes. There's several hack-ish ways I can see to do this, for example having a __setattr__ method such as
def __setattr__(self, attribute, value):
if not attribute in self.__dict__:
print "Cannot set %s" % attribute
else:
self.__dict__[attribute] = value
and then editing __dict__ directly inside __init__, but I was wondering if there is a 'proper' way to do this?
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
Slots is the way to go:
The pythonic way is to use slots instead of playing around with the __setter__. While it may solve 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'
Notes:
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.
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 has an instance layout defined in C code (like list, tuple or int).
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
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!
The proper way is to override __setattr__. That's what it's there for.
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
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)
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
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
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
None of the answers mention the performance impact of overriding __setattr__, which can be an issue when creating many small objects. (And __slots__ would be the performant solution but limits pickle/inheritance).
So I came up with this variant which installs our slower settatr after init:
class FrozenClass:
def freeze(self):
def frozen_setattr(self, key, value):
if not hasattr(self, key):
raise TypeError("Cannot set {}: {} is a frozen class".format(key, self))
object.__setattr__(self, key, value)
self.__setattr__ = frozen_setattr
class Foo(FrozenClass): ...
If you don't want to call freeze at the end of __init__, if inheritance is an issue, or if you don't want it in vars(), it can also be adapted: for example here is a decorator version based on the pystrict answer:
import functools
def strict(cls):
cls._x_setter = getattr(cls, "__setattr__", object.__setattr__)
cls._x_init = cls.__init__
#functools.wraps(cls.__init__)
def wrapper(self, *args, **kwargs):
cls._x_init(self, *args, **kwargs)
def frozen_setattr(self, key, value):
if not hasattr(self, key):
raise TypeError("Class %s is frozen. Cannot set '%s'." % (cls.__name__, key))
cls._x_setter(self, key, value)
cls.__setattr__ = frozen_setattr
cls.__init__ = wrapper
return cls
#strict
class Foo: ...
I wrote pystrict as a solution to this problem. It's too large to paste all of the code in stackoverflow.
pystrict is a pypi installable decorator that can be used with classes to freeze them. Many solutions here don't properly support inheritance.
If __slots__ doesn't work for you (because of inheritance issues), this is a good alternative.
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
#dataclass(slots=True) Nirvana (Python 3.10)
I'm in love with this #dataclass thing:
main.py
from dataclasses import dataclass
#dataclass(slots=True)
class C:
n: int
s: str
c = C(n=1, s='one')
assert c.n == 1
assert c.s == 'one'
c.n == 2
c.s == 'two'
c.asdf = 2
Outcome:
Traceback (most recent call last):
File "/home/ciro/main.py", line 15, in <module>
c.asdf = 2
AttributeError: 'C' object has no attribute 'asdf'
Note how #dataclass only requires use to define our attributes once with type annotations
n: int
s: str
and then, without any repetition we get for free:
def __init__(n, s):
self.n = n
self.s = s
__slots__ = ['n', 's']
Other free things not shown in this example:
__str__
__eq__: Compare object instances for equality by their attributes
__hash__ if you also use frozen=True: Object of custom type as dictionary key
Tested on Python 3.10.7, Ubuntu 22.10.

Categories