How can I make an existing object read-only?

I

Irmen de Jong

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.
 
A

Alex Martelli

Irmen de Jong said:
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
 
S

Steven Bethard

Irmen de Jong said:
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:
.... def __init__(self, x):
.... self.x = x
.... 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-only1

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
 
I

Irmen de Jong

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 :eek:)
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.
 
E

Eli Stevens (WG.c)

Irmen said:
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 :eek:)

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
 
A

Alex Martelli

Irmen de Jong said:
...
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
 
A

Alex Martelli

Eli Stevens (WG.c) said:
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
 
D

Duncan Booth

Alex said:
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.
 
I

Irmen de Jong

Duncan said:
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
 
I

Irmen de Jong

Alex said:
...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.
 
A

Alex Martelli

Irmen de Jong said:
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
 
I

Irmen de Jong

Alex said:
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" ?

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
 
A

Alex Martelli

Irmen de Jong said:
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
 
D

Dave Pawson

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!
 
A

Alex Martelli

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

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
 
J

Jacek Generowicz

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 ?
 
A

Alex Martelli

Jacek Generowicz said:
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
 
J

Jon Nicoll

(e-mail address removed) (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
 
R

Roger

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


Will the book also include an appendix on anti patterns?

Roger
 
A

Alex Martelli

Roger said:
...
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
 

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

Forum statistics

Threads
473,780
Messages
2,569,611
Members
45,265
Latest member
TodLarocca

Latest Threads

Top