Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How can I make an existing object read-only?

328 views
Skip to first unread message

Irmen de Jong

unread,
Oct 4, 2004, 4:55:21 PM10/4/04
to
Maybe I'm way off track here, but I want to do something like this:

Say I have an object that's initialized with a bunch of attributes.
I want to pass the object to a piece of code that is not allowed
to add/delete/set attributes on this object.
When control returns, the code is again allowed to make changes
to the object.

I know; "we're all adults, just don't change the attributes!", but
the following code more or less seems to work:

class ReadOnly:
....
def __setattr__(self, name, value):
if self._page_readonly:
raise AttributeError, "page object is read-only"
else:
self.__dict__[name] = value
....

and then controlling the setattr by setting _page_readonly.

Now I was wondering: isn't there another way?
Because the objects involved have quite a few attributes,
and initializing the objects will now call my custom setattr
method for every attribute. The code concerned is more or less
time-critical so I'd rather not have this custom setattr method
when initializing the object...

Is there another way to hack this? Am I making sense at all? ;-)


--Irmen.

Alex Martelli

unread,
Oct 4, 2004, 5:20:10 PM10/4/04
to
Irmen de Jong <irmen@-nospam-remove-this-xs4all.nl> wrote:

> Maybe I'm way off track here, but I want to do something like this:
>
> Say I have an object that's initialized with a bunch of attributes.
> I want to pass the object to a piece of code that is not allowed
> to add/delete/set attributes on this object.
> When control returns, the code is again allowed to make changes
> to the object.

Instead of passing the bare object, pass it wrapped into something like:

class nowrite:
def __init__(self, obj): self.__dict__['_obj'] = obj
def __getattr__(self, n): return getattr(self._obj, n)
def __setattr__(self, n, *args): raise AttributeError, n
__delattr__ = __setattr__

Of course this only helps avoid accidents, it doesn't give security
against malicious attacks (there's no known way to do that today in
Python). I'm also assuming that the "piece of code" IS allowed to
access any attribute and call any method of obj (including methods which
might internally set attributes, whatever), if you want to add further
prohibitions that shouldn't be too hard, though.


Alex

Steven Bethard

unread,
Oct 4, 2004, 5:22:48 PM10/4/04
to pytho...@python.org
Irmen de Jong <irmen <at> -nospam-remove-this-xs4all.nl> writes:
> Say I have an object that's initialized with a bunch of attributes.
> I want to pass the object to a piece of code that is not allowed
> to add/delete/set attributes on this object.
> When control returns, the code is again allowed to make changes
> to the object.

Would a proxy object work for you? I'm thinking something along the lines of:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252151

Start with a class like:

class Proxy(object):
def __init__(self, obj):
super(Proxy, self).__init__(obj)
self.__dict__['_obj'] = obj
def __getattr__(self, attr):
return getattr(self._obj, attr)
def __setattr__(self, attr, val):
raise AttributeError, "object is read-only"

Then you can get the following behavior:

>>> class S(object):
... def __init__(self, x):
... self.x = x
...
>>> s = S(1)
>>> s.x
1
>>> s.x = 2
>>> s.x
2
>>> p = Proxy(s)
>>> p.x
2
>>> p.x = 3
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
File "D:\Steve\My Programming\python\temp.py", line 8, in __setattr__
self._obj = obj
AttributeError: object is read-only
>>> s.x = 1
>>> p.x
1

Basically the idea here is that you don't actually pass your object to the
other piece of code; you pass a wrapper object that acts like your object.
This means that you only get the __setattr__ overhead in the other code, but
you do also get the overhead of the additional lookup (proxy._obj instead of
just _obj).

I don't know if these are acceptable costs to you, but it is, at least, an
alternative, I think.

Steve

Irmen de Jong

unread,
Oct 4, 2004, 5:47:37 PM10/4/04
to
Alex Martelli wrote:
(and Steven Bethard also, essentially)

> Instead of passing the bare object, pass it wrapped into something like:
>
> class nowrite:
> def __init__(self, obj): self.__dict__['_obj'] = obj
> def __getattr__(self, n): return getattr(self._obj, n)
> def __setattr__(self, n, *args): raise AttributeError, n
> __delattr__ = __setattr__
>

Now that was simple, why didn't I think of that myself.
Too much Java coding apparently clogs up your mind :o)

> Of course this only helps avoid accidents, it doesn't give security
> against malicious attacks (there's no known way to do that today in
> Python).

Yes, I am aware of that. It was purely to 'avoid accidents'.

Incidentally, why are you writing:

def __setattr__(self, n, *args): ...

with a varg list ? I always assumed it only got a single 'value' parameter?


Thanks

--Irmen.

Eli Stevens (WG.c)

unread,
Oct 4, 2004, 9:44:31 PM10/4/04
to pytho...@python.org
Irmen de Jong wrote:
> Alex Martelli wrote:
> (and Steven Bethard also, essentially)
>
>> Instead of passing the bare object, pass it wrapped into something like:
>>
>> class nowrite:
>> def __init__(self, obj): self.__dict__['_obj'] = obj
>> def __getattr__(self, n): return getattr(self._obj, n)
>> def __setattr__(self, n, *args): raise AttributeError, n
>> __delattr__ = __setattr__
>>
>
> Now that was simple, why didn't I think of that myself.
> Too much Java coding apparently clogs up your mind :o)

Quite (I do Java during my day job ;).

Another alternative that actually locks the object down, could look
something like this (of course, all the prints need to get nuked, but
they helped me see what was going on while coding this up):


class Foo(object):
def __init__(self):
# Needed to jumpstart the whole thing
self.__dict__["_CurrentState__setattr__"] = self._readWriteSetattr
self.bar = "bar"
self.baz = "baz"

def ReadWrite(self):
print "ReadWrite"
self._CurrentState__setattr__ = self._readWriteSetattr

def ReadOnly(self):
print "ReadOnly"
self._CurrentState__setattr__ = self._readOnlySetattr

def __setattr__(self, name, value):
self._CurrentState__setattr__(name, value)

def _readWriteSetattr(self, name, value):
print "_readWriteSetattr", name, value
self.__dict__[name] = value

def _readOnlySetattr(self, name, value):
print "_readOnlySetattr", name, value
if name == "_CurrentState__setattr__":
self.__dict__[name] = value
else:


raise AttributeError, "page object is read-only"


While coding this, I learned that:

self.__setattr__ = self._readOnlySetattr

Doesn't DWIM. ;) Hence the _CurrentState__setattr__ hack. Is this
pythonic? Dunno. Thoughts?

I also don't know which would be faster between the proxy and having an
extra level of indirection in __setattr__. I would guess it would
depend on if you do more reads or writes in the tight loops. If your
code is read-heavy, this might actually be a win.

Of course, if you are _really_ concerned with performance, you might
just turn the whole protection thing off when in production.

HTH,
Eli

Alex Martelli

unread,
Oct 5, 2004, 2:04:31 AM10/5/04
to
Irmen de Jong <irmen@-nospam-remove-this-xs4all.nl> wrote:
...

> > def __setattr__(self, n, *args): raise AttributeError, n
> > __delattr__ = __setattr__
...

> Incidentally, why are you writing:
>
> def __setattr__(self, n, *args): ...
>
> with a varg list ? I always assumed it only got a single 'value' parameter?

Just to be able to reuse the same method as __delattr__ in the very next
line; could have coded 'v=None', say, equivalently to '*args'.


Alex

Alex Martelli

unread,
Oct 5, 2004, 2:13:19 AM10/5/04
to
Eli Stevens (WG.c) <lis...@wickedgrey.com> wrote:
...

> def ReadWrite(self):
> print "ReadWrite"
> self._CurrentState__setattr__ = self._readWriteSetattr
>
> def ReadOnly(self):
> print "ReadOnly"
> self._CurrentState__setattr__ = self._readOnlySetattr
>
> def __setattr__(self, name, value):
> self._CurrentState__setattr__(name, value)
...

> self.__setattr__ = self._readOnlySetattr
>
> Doesn't DWIM. ;) Hence the _CurrentState__setattr__ hack. Is this
> pythonic? Dunno. Thoughts?
>
> I also don't know which would be faster between the proxy and having an
> extra level of indirection in __setattr__. I would guess it would
> depend on if you do more reads or writes in the tight loops. If your
> code is read-heavy, this might actually be a win.
>
> Of course, if you are _really_ concerned with performance, you might
> just turn the whole protection thing off when in production.

...or implement it by changing the object's *class*...:

class RO_class(TrueClass):
def makeReadWrite(self): self.__class__ = TrueClass
def __setattr__(self, n, v=''): raise AttributeError, n
__delattr__ = __setattr__

def makeReadOnly(obj): obj.__class__ = RO_class

This is a very Pythonic approach to dealing with an object that exists
in two 'states', each rather persistent and with different behavior; it
is better performing and neater than using an obj._state and having 'if
self._state: ... else: ...' statements strewn around.

Your approach (switching boundmethods instead of a passive state flag or
the class itself) is somewhere in-between -- it may require indirection
when special methods are involved (since, in the new object model,
Python looks special methods up per-class, not per-instance), it may
require switching more than one method (more flexible if needed, but
otherwise boilerplatey if you always switch methods as a group).

As I recall, the early prototypes for the new object model (pre-release
2.2's) had obj.__class__ as read-only; it was changed to read/write
specifically to enable this idiom -- a fact which appears to me to
support the contention that this IS a highly Pythonic approach, not just
a hack which accidentally happens to work...


Alex

Duncan Booth

unread,
Oct 5, 2004, 10:40:54 AM10/5/04
to
Alex Martelli wrote:

> Irmen de Jong <irmen@-nospam-remove-this-xs4all.nl> wrote:
>
>> Maybe I'm way off track here, but I want to do something like this:
>>
>> Say I have an object that's initialized with a bunch of attributes.
>> I want to pass the object to a piece of code that is not allowed
>> to add/delete/set attributes on this object.
>> When control returns, the code is again allowed to make changes
>> to the object.
>
> Instead of passing the bare object, pass it wrapped into something like:
>

<snip>

An alternative is just to pass a copy of the object. That way the recipient
can do what they like with it, but the calling code won't see any changes.

Irmen de Jong

unread,
Oct 5, 2004, 12:43:59 PM10/5/04
to
Duncan Booth wrote:
> An alternative is just to pass a copy of the object. That way the recipient
> can do what they like with it, but the calling code won't see any changes.

Agreed, but then any attempts to change the attributes will go
unnoticed. I wanted a solution that would catch these attempts
and trow an exception (perhaps this wasn't clear enough in
my original question).

Thanks anyway.

--Irmen

Irmen de Jong

unread,
Oct 5, 2004, 3:00:57 PM10/5/04
to
Alex Martelli wrote:

> ...or implement it by changing the object's *class*...:
>
> class RO_class(TrueClass):
> def makeReadWrite(self): self.__class__ = TrueClass
> def __setattr__(self, n, v=''): raise AttributeError, n
> __delattr__ = __setattr__
>
> def makeReadOnly(obj): obj.__class__ = RO_class
>
> This is a very Pythonic approach to dealing with an object that exists
> in two 'states', each rather persistent and with different behavior; it
> is better performing and neater than using an obj._state and having 'if
> self._state: ... else: ...' statements strewn around.

I think this the kind of solution I think I was searching for!
And should have been able to think up myself.

I really should dust off my old GOF Desing Patterns book,
because your code made me remember the "state" pattern right away :-)

There should be a Python version of that book... I think that
the patterns in it are very easily expressed in Python, and
that Python allows for many other interesting patters ('Borg', to
name one).
This "state" pattern needs a clumsy internal state object in C++
but in Python we can just change the object's class directly,
which is a much more direct implementation of what the pattern
tries to achieve :-)

Thanks Alex

--Irmen de Jong.

Alex Martelli

unread,
Oct 5, 2004, 5:56:19 PM10/5/04
to
Irmen de Jong <irmen@-nospam-remove-this-xs4all.nl> wrote:
...

> I really should dust off my old GOF Desing Patterns book,
> because your code made me remember the "state" pattern right away :-)
>
> There should be a Python version of that book... I think that

Yep -- I've been writing about DPs with/for Python for years now (that's
the invariable background to almost all of my conference talks &c, check
most of them out at www.strakt.com), and one day (once I'm done doing
2nd editions of cookbooks and nutshells;-) I hope to write a book about
that (plus materials on agile programming practices, again with Python
as the explanatory language). That's what I _wanted_ to write from day
one, but I'm glad it didn't materialize yet... it will be a much better
book for the wait;-). Meanwhile Robert Martin has already written a
book far closer than any other to what I had in mind -- but he uses C++
and Java as explanatory languages, and _that_ will give MY book enough
of an edge to almost catch up with Uncle Bob's awesome experience and
writing skills...!-)

> the patterns in it are very easily expressed in Python, and
> that Python allows for many other interesting patters ('Borg', to
> name one).

Yep, I'm reasonably familiar with that one;-)

> This "state" pattern needs a clumsy internal state object in C++
> but in Python we can just change the object's class directly,
> which is a much more direct implementation of what the pattern
> tries to achieve :-)

Right, good point. In C++ you can do something closer with the Strategy
pattern -- still one level of indirectness, but a consistent set of
methods (embodied in the one strategy object) commuted as a whole.
Assigning __class__ IS of course more direct, yes;-).

> Thanks Alex

You're most welcome -- thank YOU for your feedback!


Alex

Irmen de Jong

unread,
Oct 5, 2004, 6:40:13 PM10/5/04
to
Alex Martelli wrote:
> Meanwhile Robert Martin has already written a
> book far closer than any other to what I had in mind -- but he uses C++
> and Java as explanatory languages, and _that_ will give MY book enough
> of an edge to almost catch up with Uncle Bob's awesome experience and
> writing skills...!-)

A bit of searching found this book:
"Agile Software Development, Principles, Patterns, and Practices" ?


>>This "state" pattern needs a clumsy internal state object in C++
>>but in Python we can just change the object's class directly,
>>which is a much more direct implementation of what the pattern
>>tries to achieve :-)
>
>
> Right, good point. In C++ you can do something closer with the Strategy
> pattern -- still one level of indirectness, but a consistent set of
> methods (embodied in the one strategy object) commuted as a whole.
> Assigning __class__ IS of course more direct, yes;-).

I just flipped the page in the GOF DP book, having read about "State",
and there it was: "Strategy". Interestingly enough, the only difference
-at first glance- with "State" is that strategy's 'Context' object
(the object that contains the reference to the strategy object),
has a different interface than with the State pattern. With state it
is essentially identical to the internal state object -- which,
in Python, is just the new class we assign to the object ;-)

State is about changing an object's class at runtime, to make it do
something different when the object is "in a different state".
Strategy is about being able to switch or add internal algorithms
without having to change the code that uses it.
So say the GOF. Amen. :-D

Don't know why I'm writing all this. I have some reading to do...


--Irmen

Alex Martelli

unread,
Oct 5, 2004, 7:08:05 PM10/5/04
to
Irmen de Jong <irmen@-nospam-remove-this-xs4all.nl> wrote:

> Alex Martelli wrote:
> > Meanwhile Robert Martin has already written a
> > book far closer than any other to what I had in mind -- but he uses C++
> > and Java as explanatory languages, and _that_ will give MY book enough
> > of an edge to almost catch up with Uncle Bob's awesome experience and
> > writing skills...!-)
>
> A bit of searching found this book:
> "Agile Software Development, Principles, Patterns, and Practices" ?

Yep, that one. His masterpiece so far, IMHO. Pity about all the C++
and Java therein;-).


> State is about changing an object's class at runtime, to make it do
> something different when the object is "in a different state".
> Strategy is about being able to switch or add internal algorithms
> without having to change the code that uses it.
> So say the GOF. Amen. :-D
>
> Don't know why I'm writing all this. I have some reading to do...

Writing may help fix one's ideas and thus help one's reading;-).


Alex

Dave Pawson

unread,
Oct 6, 2004, 2:06:15 AM10/6/04
to Alex Martelli, pytho...@python.org
On Tue, 2004-10-05 at 22:56, Alex Martelli wrote:

> Yep -- I've been writing about DPs with/for Python for years now (that's
> the invariable background to almost all of my conference talks &c, check
> most of them out at www.strakt.com), and one day (once I'm done doing
> 2nd editions of cookbooks and nutshells;-) I hope to write a book about
> that

Wouldn't it be nice if there was a wiki to collect them on Alex!

--
Regards DaveP.


Alex Martelli

unread,
Oct 6, 2004, 2:57:39 AM10/6/04
to
Dave Pawson <da...@dpawson.co.uk> wrote:

There's a great wiki on Patterns (indeed I think it was historically the
first one -- the Portland Patterns Repository, I believe) and I'm sure
they're not language bigots, so one could try going there.

Personally, I'm smack in the middle of editing/coordinating/writing a
multi-authors book for the 2nd time -- indeed, the Python Cookbook is
rather exceptional in the extent of its multi-authorness! -- and I'm not
keen for a 3rd run... it's fascinating but also a mess. Just imagine
one contributor not answering mail (to send a proper intellectual
property release when asked), or deciding he doesn't want to after all,
holding up the whole project... *shudder*. And, with a Wiki, authorship
of each word, each sentence may be even harder to trace.

I think I'll do the book myself, or with one well-identified co-author,
though of course I'll put up pieces in more discursive form to get
feedback &c...


Alex

Jacek Generowicz

unread,
Oct 6, 2004, 4:11:56 AM10/6/04
to
ale...@yahoo.com (Alex Martelli) writes:

Isn't Strategy more like rebinding the methods of a class, rather than
rebinding the .__class__ of an instance ?

Alex Martelli

unread,
Oct 6, 2004, 5:14:44 AM10/6/04
to
Jacek Generowicz <jacek.ge...@cern.ch> wrote:
...

> > Right, good point. In C++ you can do something closer with the Strategy
> > pattern -- still one level of indirectness, but a consistent set of
> > methods (embodied in the one strategy object) commuted as a whole.
> > Assigning __class__ IS of course more direct, yes;-).
>
> Isn't Strategy more like rebinding the methods of a class, rather than
> rebinding the .__class__ of an instance ?

<http://c2.com/cgi/wiki?StrategyPattern> has lots of discussion of State
vs Strategy (and no clear conclusion -- discussants disagree deeply on
philosophical grounds). My viewpoint is that each is about objectifying
behavior -- as is delegating some or all behavior to another object.

Lookups of attributes (including methods) on an instance implicitly
delegate to the __class__ unless the attribute is found directly on the
instance [[lookups of special methods to implement operators &c don't
even look at the instance except in classic classes, but we can simply
focus on ordinary lookups here]]. Switching classes is thus switching a
bundle of attributes (particularly methods) as a whole, and affects only
the specific instance whose class changes, while switching a single
method on a class obviously has finer granularity on one plane (affects
a specific facet only) but HUGE on the other (affects any number of
instances at once).

I don't see anything in Strategy that suggests a desire to change the
behavior of an arbitrarily high number of instances at one stroke,
rather than the behavior of one single, specific instance. So, I can't
really see the parallel you suggest. Setting a bound method on an
instance, without altering that instance's class object at all, might be
closer, and as long as we're ignoring [[that part]] the parallel between
the techniques is there -- it's a matter of granularity (do you want to
affect each facet of behavior, or instance behavior as a whole). The
commonality is in using implicit delegation, rather than explicit
(either via __getattr__ or method-by-method).

Basically, Python gives you more viable strategies than C++, so any
one-to-one mapping between usage in the two languages is doomed from the
start -- there will often be more than one strategy in Python that maps
down to the same strategy in C++. Personally, I find that (for the
categories of programs I generally write) the meaningful unit of
behavior is rarely a single method (it happens, but it's not typical) --
there are generally _bundles_ (of methods representing subfacets of a
single "behavior") that must stay coupled and cohesive; so, switching
methods individually is not as attractive as switching a set of them
through a single object... and changing __class__ does exactly this for
the largest bundle (thus, ideal for a single-responsibility class... and
shouldn't those be the ones we should _strive_ to design?-).


Alex

Jon Nicoll

unread,
Oct 6, 2004, 6:38:49 AM10/6/04
to
ale...@yahoo.com (Alex Martelli) wrote in message

[...]


> I think I'll do the book myself, or with one well-identified co-author,
> though of course I'll put up pieces in more discursive form to get
> feedback &c...
>

It's a quote from someone else on c.l.p, but applies here, I think:

"I would buy two copies of any book written by Alex Martelli, sight unseen..."

Go for it Alex!

Regards
Jon N

Roger

unread,
Oct 6, 2004, 6:03:27 PM10/6/04
to
ale...@yahoo.com (Alex Martelli) wrote in message news:<1gl88is.1sxl7371uqtnyzN%ale...@yahoo.com>...


Will the book also include an appendix on anti patterns?

Roger

Alex Martelli

unread,
Oct 7, 2004, 12:27:59 AM10/7/04
to
Roger <rlwm....@home.nl> wrote:
...

> > > > Yep -- I've been writing about DPs with/for Python for years now (that's
> > > > the invariable background to almost all of my conference talks &c, check
> > > > most of them out at www.strakt.com), and one day (once I'm done doing
> > > > 2nd editions of cookbooks and nutshells;-) I hope to write a book about
...

> Will the book also include an appendix on anti patterns?

I think it's a good idea to point out how things may go wrong, yes,
though I'm not sure if an appendix is the right location for that.


Alex

Johannes Eble

unread,
Oct 7, 2004, 3:59:37 AM10/7/04
to
Hello Alex,

On Tue, 5 Oct 2004 23:56:19 +0200, ale...@yahoo.com (Alex Martelli)
wrote:

>Irmen de Jong <irmen@-nospam-remove-this-xs4all.nl> wrote:
> ...
>> I really should dust off my old GOF Desing Patterns book,
>> because your code made me remember the "state" pattern right away :-)
>>
>> There should be a Python version of that book... I think that
>
>Yep -- I've been writing about DPs with/for Python for years now (that's
>the invariable background to almost all of my conference talks &c, check
>most of them out at www.strakt.com), and one day (once I'm done doing
>2nd editions of cookbooks and nutshells;-) I hope to write a book about
>that (plus materials on agile programming practices, again with Python
>as the explanatory language).

Will your book also be influenced by Design Patterns in Dynamic
Languages e.g. Common Lisp? Peter Norvig argues that 16 GoF Patterns
are simply workarounds of C++ like language shortcomings. It would be
very interesting to see such a discussion for Python. Also, Norvig's
slides are somewhat too terse for me (as well as Lisp macros etc.).
It's a pity that most of the pattern books are based on the GoF book
only.
See

http://norvig.com/design-patterns/


Best regards


Johannes

Alex Martelli

unread,
Oct 7, 2004, 3:59:03 AM10/7/04
to
Johannes Eble <skywalke...@hotmail.com> wrote:
...

> >Yep -- I've been writing about DPs with/for Python for years now (that's
> >the invariable background to almost all of my conference talks &c, check
> >most of them out at www.strakt.com), and one day (once I'm done doing
> >2nd editions of cookbooks and nutshells;-) I hope to write a book about
> >that (plus materials on agile programming practices, again with Python
> >as the explanatory language).
>
> Will your book also be influenced by Design Patterns in Dynamic
> Languages e.g. Common Lisp? Peter Norvig argues that 16 GoF Patterns
> are simply workarounds of C++ like language shortcomings. It would be
> very interesting to see such a discussion for Python. Also, Norvig's
> slides are somewhat too terse for me (as well as Lisp macros etc.).
> It's a pity that most of the pattern books are based on the GoF book
> only.

I recommend The Design Patterns Smalltalk Companion, an excellent book.
And yes, I have the same observation on some of _my_ too-terse slides
(at www.strakt.com): some classic patterns are workarounds for static
typing (and, also, for non-firstclassness of some constructs, to a
lesser extent).

Outside of the GoF's book, there's actually a wealth of pattern books.
Fowler's "Analysis Patterns" and "Refactoring", all the PLoP books,
Schmidt's work (http://www.cs.wustl.edu/~schmidt/patterns-ace.html),
etc, etc...


Alex

0 new messages