1

I'd like to set an instance attribute in __init__ and then disallow any change to it. Is there such a mechanism in Python OOP. More specifically, I am using Python 3.7.

Before you down vote it as a duplicate, note that I am talking about the immutability of a instance attribute instead of that of the class instance themselves like the suggested similar question titles were asking about.

1
  • 1
    There is no good way to make this absolutely safe, but generally in Python, you'd use a property and not define a setter. Commented Jan 14, 2019 at 1:34

1 Answer 1

1

There are multiple options depending on exactly what you are trying to do.

1. Final type

Use mypy and the Final type from typing_extensions. This is only checked statically with mypy, not enforced at runtime.

$ pip install typing_extensions

from typing_extensions import Final

class Spam:
    foo: Final[int]
    def __init__(self, foo):
        self.foo = foo

This should work as of this writing, but the typing_extensions are still kind of experimental, and may change. Guido himself has been working on mypy, so it might even end up in the standard library typing module.

2. @property

Use a property (which can be circumvented, but probably not on accident) either--

Without a setter. You still have to store the variable somewhere. Maybe with a _-prefixed name, or in a closure, or directly on the instance dict with vars (which will bypass the descriptor function from @property).

class Spam:
    def __init__(self, foo):
        vars(self)['foo'] = foo
    @property
    def foo(self):
        return vars(self)['foo']

Or with a setter that checks if it's already set, and raises an exception if it is. This lets you set it the first time with the normal syntax, but it's probably not worth the bother if you're setting it in __init__.

3. subclass tuple

Inherit from tuple. You'll have to use __new__ instead of __init__. You get real immutability, but these are no longer attributes per se and you have to access them with integer subscripts (self[0] etc.). Inheriting from tuple means you get all the tuple methods too, which you may not want.

If you still want the dot access, you can try creating a collections.namedtuple instead, and inherit from that.

from collections import namedtuple

class Spam(namedtuple('Spam', 'foo')):
    def __new__(cls, foo):
        return super().__new__(cls, foo)
    def print_foo(self):
        print(self.foo)

Of course, you inherit even more methods this way.

See also, typing.NamedTuple.

Sign up to request clarification or add additional context in comments.

10 Comments

3. you mean like class MyClass(tuple)? This way, can I still access these values with a name label such as print(my_object.name)?
For #2, you can store the data as my_instance.__dict__['property_name'] then you really can't mess with it unintentionally.
@MadPhysicist, but this way, I can still do my_instance.property_name = xxx out of carelessness, or am I missing the point here?
@Indominus. Just keep in mind that the first argument to a namedtuple is a class name and the second is a list of field names. You can create the field names outside the actual class declaration.
@MadPhysicist the only way I can imagine it being unreliable is if you shadow the vars builtin for some reason. That goes for any builtin though. I also think next(foo) or str(foo) are usually preferable to foo.__next__() or foo.__str__(). We have these builtins for a reason.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.