Easy immutability in python?

T

Terry Hancock

Is there an *easy* way to make an object immutable in
python? Or perhaps I should say "one obvious way to do it"?
Oughtn't there to be one?

I've found a thread on how to do this[1], which essentially
says something like "redefine __setattr__, __delattr__,
__hash__, __eq__, __setitem__, delitem__ ... and probably
some other stuff too".

[1]

Yet, what you have to do is pretty mechanical (e.g. all the
mutators have to raise NotImplementedError and the hashes
and comparisons seem like they have obvious defaults).

Given that I have some kind of container, like an overloaded
list, and I just say "oh, I need that to be immutable, like
a tuple" -- it would be nice to just be able to declare it
so. I just want this for QA purposes -- I have a pretty
complex system, and I want the programmer to be warned when
he accidentally tries to change an immutable enumeration or
redefine a symbol.

I know a (hard and error-prone) way to do it, but I'm
thinking there must be a "smart" way to do it.

Is there some way to do that with, e.g. decorators?

(I haven't really figured out why I should want decorators,
is this a use case?).

Or maybe I should make an "Immutable" class, and just
inherit from it last so it overloads everything else with
the null interfaces?
 
J

jess.austin

Since this is a container that needs to be "immutable, like a tuple",
why not just inherit from tuple? You'll need to override the __new__
method, rather than the __init__, since tuples are immutable:

class a(tuple):
def __new__(cls, t):
return tuple.__new__(cls, t)

cheers,
Jess
 
J

jess.austin

To be clear, in this simple example I gave you don't have to override
anything. However, if you want to process the values you place in the
container in some way before turning on immutability (which I assume
you must want to do because otherwise why not just use a tuple to begin
with?), then that processing should take place in a.__new__.

cheers,
Jess
 
T

Terry Hancock

On 4 Mar 2006 10:14:56 -0800
Since this is a container that needs to be "immutable,
like a tuple", why not just inherit from tuple? You'll
need to override the __new__ method, rather than the
__init__, since tuples are immutable:

class a(tuple):
def __new__(cls, t):
return tuple.__new__(cls, t)

The reason this is undesireable, is because in the typical
use-case, I want to have TWO kinds of objects, one mutable,
and one immutable. Just like tuple / list. I would prefer
not to have to implement them completely separately. So
far, it's usually simpler to implement the mutable type
first -- the need for immutability semantics usually arises
after you have a mutable version.

But undoing mutability apparently is like making a sieve
watertight -- it would've been easier to design a ladle and
then put holes in it, than to design a sieve and then stop
up the holes.

In fact, my particular case wants to subclass Set and
ImmutableSet -- their contents, though, are also immutables
in principle. In fact the elements are similar to "int" or
"str", but I don't want to subclass those, because I don't
want them to have math methods or string methods.

I've considered several different approaches to this. One is
to create a "BaseVocabulary" as an abstract class, from
which the mutable and immutable types are drawn, and then
mix it in with Set and ImmutableSet:

class BaseVocabulary(object):
def __init__(self, values):
self._data = values
# (or something like that)
# other functionality

class Vocabulary(Set, BaseVocabulary):
# stuff appropriate for the "open" vocabulary
pass

class Enumeration(ImmutableSet, BaseVocabulary):
# stuff appropriate for the "closed" enumeration
pass

This is bad, both aesthetically and practically.
Aesthetically, because "abstract classes stink of Java" and
pragmatically because the __init__ from BaseVocabulary
will generally not work for the immutable case.

So I either have to rewrite it special, or go back in
time and fix the original, knowing that I'm going to want
an immutable variant.

Again with the aesthetics, it's just ugly that I can't
mutate an object in its __init__ and then make it immutable
after I'm done setting it up. The approach with __new__ and
using the superclass's __setattr__ to set values is nasty
boilerplate-rich cruft which forces me to go back and
completely re-implement the same functionality in a
completely different way, just because of something that
seems like a simple conceptual change (i.e. "don't overwrite
or extend this thing -- make a new one if you need a
change").

I even tried defining __setattr__ at the end of the
__init__, but that doesn't seem to work (or I'm doing it
wrongly).

Right now I'm leaning towards making a class "Immutable"
with all the mutator methods raising NotImplementedError,
and then subclass from this so that it overrides the
necessary methods. It's still going to mess with me, though,
because it will not allow the __init__ to work as planned,
and I'll have to go back and redesign the base class to work
with or without immutability.

Alternatively, I could just decide to change my practice and
implement ALL objects on the assumption that they will be
immutable, and add *mutability* after-the-fact. But that
seems like extraordinarily bad practice, since mutability is
usually the better default.

I'm going to dig through the sets module code to see how it
does this, but what little I've seen gives me a bad feeling
that this simple idea is hard to implement simply in Python
-- and that strikes me as a "wart", yes.

So, I'm wondering if there is a simple way after all.

1) Is there an idiom for this that I just don't know?

2) Have recent changes to the language made it easier?
(__new__ for example, is apparently new, or at least
newly documented -- but as I say, I'm not sure it's
the most practical solution for this problem).

3) Is there a "remarkably clever" way that I can tack
on, even if it isn't exactly simple?

and only in the unlikely event that answers 1-3 are all
"no", would I ask:

4) And as a last resort, if it really is hard, why?
Shouldn't a simple idea to express in English be easy to
express in Python? If it's really impossibly difficult,
maybe Python should provide a means to implement it.

Cheers,
Terry
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top