make an object read only

Steven D'Aprano steve+python at pearwood.info
Tue Aug 2 12:36:30 EDT 2016


On Wed, 3 Aug 2016 01:12 am, Robin Becker wrote:

> A reportlab user found he was doing the wrong thing by calling canvas.save
> repeatedly, our documentation says you should not use Canvas objects after
> the save method has been used. The user had mixed results :( 
> 
> It would be better to make the canvas object completely immutable all the
> way down when save has been called, but I think that's quite hard as these
> objects have quite a large and varied set of attributes, lists other
> objects dictionaries etc etc.

If save() is the only problematic method, that's easy to fix:

class X:
    def _bad_save(self):
        raise RuntimeError("I told you not to call save() more than once!")

    def save(self):
        # do the actual saving
        # then replace the method:
        self.save = self._bad_save


Hmmm... clever as that is, it's probably not clever enough, as users can
still call the unbound save method:

X.save(instance)

So back to the really old-fashioned way:

class X:
    def __init__(self):
        self._saved = False

    def save(self):
        if self._saved:
            raise RuntimeError("*wack with clue stick*")
        else:
            # do the actual saving
            self._saved = True


> I can think of some strategies for making the object unusable eg changing
> it's class and getting rid of __dict__, but that disallows referencing
> valid properties eg pagesize, fontName, etc etc.

It shouldn't disallow anything.

class X:
     # implementation of everything, properties, etc.
     # as they are expected to work before saving

     def save(self):
         # do the actual save
         self.__class__ = SavedX


class SavedX(X):
    # Override just the bits that need neutering
    # including save

    def save(self):
        raise RuntimeError("stop that!")



The tricky bit may be preventing writes to the instance __dict__. I don't
think it is practical to allow the user to add arbitrary attributes to the
instance up to the point they call save(), then prevent them from doing the
same. You might be able to get it to work, but with difficulty.

Better to fix save() so you can call it multiple times without breaking the
instance. If you can't do that, the least-worst alternative would be to use
__slots__ for both X and SavedX so that the user can't add new attributes
at all.

But really, the right way to do this is to fix the class so that save() can
be called multiple times. In your editor, does selecting Save twice corrupt
the file you're working on?


> Is there a way to recursively turn everything immutable?

First you have to come up with a way to turn everything immutable. (Good
luck with that.) Then apply it recursively to the object and all its
attributes.




-- 
Steve
“Cheer up,” they said, “things could be worse.” So I cheered up, and sure
enough, things got worse.




More information about the Python-list mailing list