Making immutable instances

M

Mike Meyer

Paul Rubin said:
I see it the other way; almost all the time in my own code I try to
initialize all of any instance's attributes in an __init__ method.
Dynamically adding attributes is useful sometimes, but done
willy-nilly leads to spaghetti code.

Pretty much anything, if abused, can lead to "spaghetti code".
In the cases where it's useful,
I'd rather see it done through a special __setattr__ method, perhaps
inherited from a mixin:

class Frob(Whatsit, DynamicAttributeMixin):
...

However, that's not likely to happen.

The thing is, the need for an extra attribute doesn't come from your
class, it comes from the client of your class. You can't know if the
client code will need that facility or not, so the only choice you
know won't be wrong sometime is to always add the mixin. In which
case, you should follow the Python "the less boilerplate, the better"
practice, and leave it off. Which is what we have.

Letting the class author declare whether or not the client can add
attributes is wrong for the same reasons - and in the same places -
that letting the class author declare that the client shouldn't be
allowed to access specific attributes, and so on, is wrong. Of
course, it's also *right* for the same reasons and in the same places
as those things. I just don't think those places happen in Python
programs.

<mike
 
P

Paul Rubin

Mike Meyer said:
Letting the class author declare whether or not the client can add
attributes is wrong for the same reasons - and in the same places -
that letting the class author declare that the client shouldn't be
allowed to access specific attributes, and so on, is wrong.

The same logic can apply to what the class operations should do. The
answer is the same in both cases. That's why subclassing and
inheritance were invented.
 
B

bonono

Mike said:
The thing is, the need for an extra attribute doesn't come from your
class, it comes from the client of your class. You can't know if the
client code will need that facility or not, so the only choice you
know won't be wrong sometime is to always add the mixin. In which
case, you should follow the Python "the less boilerplate, the better"
practice, and leave it off. Which is what we have.

Letting the class author declare whether or not the client can add
attributes is wrong for the same reasons - and in the same places -
that letting the class author declare that the client shouldn't be
allowed to access specific attributes, and so on, is wrong. Of
course, it's also *right* for the same reasons and in the same places
as those things. I just don't think those places happen in Python
programs.
I am puzzled, and could have read what you want wrong. Are you saying
you want something like this :

a={}
a.something = "I want to hang my stuff here, outside the intended use
of dict"
 
M

Mike Meyer

Paul Rubin said:
The same logic can apply to what the class operations should do. The
answer is the same in both cases. That's why subclassing and
inheritance were invented.

Exactly. Adding an attribute is isomorphic to subclassing, except it
doesn't' take any boilerplate. Unless you do something to prevent
people from subclassing, they can always write the boilerplate and
pretty much continue as before. I don't believe there's a good reason
for preventing the boilerplate free method, and repeated requests for
a use case for this have gone unsatisied.

Have you got a use case?

<mike
 
M

Mike Meyer

I am puzzled, and could have read what you want wrong. Are you saying
you want something like this :

a={}
a.something = "I want to hang my stuff here, outside the intended use
of dict"

Exactly. For a use case, consider calling select.select on lists of
file objects. If the processing is simple enough, the clearest way to
associate a handler with each socket is just to add it as an
attribute. But that doesn't work - sockets are bulitin types. So you
consider less light-weight solutions, like subclassing socket (now
that that's possible), or a dictionary of handlers keyed by socket.

This works by default with classes written in Python. That it doesn't
work for builtins is inconsistent, non-orthogonal, and
incomplete. However, it's easy to work around, and the obvious fix -
adding a dictionary to every builtin - is rather costly. So we'll live
with it since practicality beats purity.

<mike
 
B

bonono

Mike said:
Exactly. For a use case, consider calling select.select on lists of
file objects. If the processing is simple enough, the clearest way to
associate a handler with each socket is just to add it as an
attribute. But that doesn't work - sockets are bulitin types. So you
consider less light-weight solutions, like subclassing socket (now
that that's possible), or a dictionary of handlers keyed by socket.

This works by default with classes written in Python. That it doesn't
work for builtins is inconsistent, non-orthogonal, and
incomplete. However, it's easy to work around, and the obvious fix -
adding a dictionary to every builtin - is rather costly. So we'll live
with it since practicality beats purity.
While I agree with the use case(I want it sometimes too), it seems that
the language creator may also deliberately disallow that, not because
it is not doable or costly. So how, the built-in types still need to
have some form of dictionary or else how would dir(a) of the above
dictionary work ?
 
M

Mike Meyer

While I agree with the use case(I want it sometimes too), it seems that
the language creator may also deliberately disallow that, not because
it is not doable or costly. So how, the built-in types still need to
have some form of dictionary or else how would dir(a) of the above
dictionary work ?

Built-in types don't have a real dictionary. They have a C struct that
holds the various methods. The entries in the struct are called
"slots", hence the __slots__ magic attribute. That __slots__ makes it
impossible to add an attribute is documented as an implementation
detail. This makes me think that the same restriction on the builtin
types is the same.

FWIW, dir returns a list built from a number of source. But if you
look at, for example, list.__dict__, you'll notice that it's not a
dict.

<mike
 
B

bonono

Mike said:
Built-in types don't have a real dictionary. They have a C struct that
holds the various methods. The entries in the struct are called
"slots", hence the __slots__ magic attribute. That __slots__ makes it
impossible to add an attribute is documented as an implementation
detail. This makes me think that the same restriction on the builtin
types is the same.

FWIW, dir returns a list built from a number of source. But if you
look at, for example, list.__dict__, you'll notice that it's not a
dict.
Well, in this case, would it be simple for the OP that if he wants to
disallow this attaching additional things, just use __slot__.

What I wan to say though is, if we can live with the inability of not
able to attach to built-in types, why is it so difficult for other user
defined class ? If the authors go to the length of not allowing it, so
be it. They are afterall define it for their use and how someone else
will use it don't matter.
 
M

Mike Meyer

Well, in this case, would it be simple for the OP that if he wants to
disallow this attaching additional things, just use __slot__.

That's *documented* as an implementation-dependent behavior. Using it
to get that effect is abuse of the feature, and may well quit working
in the future.
What I wan to say though is, if we can live with the inability of not
able to attach to built-in types, why is it so difficult for other user
defined class ?

Because not being able to do it for built-in types is an
implementation detail, and a wart in the language.

And again, *what's the use case*? A number of people have asked why we
shouldn't allow this, but none of them been able to come up with a use
case better than "I think doing that is bad style."
If the authors go to the length of not allowing it, so be it. They
are afterall define it for their use and how someone else will use
it don't matter.

I take it you never distribute your code, or otherwise expect other
people to reuse it?

<mike
 
B

bonono

Mike said:
I take it you never distribute your code, or otherwise expect other
people to reuse it?
No, distribute when necessary. But I define it and code it the way I
want, how and others will use it, curse it doesn't matter to me.

Of course, I would give suggestion(wish list bug as in debian term)
when I use other people's stuff, and see a particular usage that is not
there. But it ends just there, wish list.
 
B

bonono

Mike said:
That's *documented* as an implementation-dependent behavior. Using it
to get that effect is abuse of the feature, and may well quit working
in the future.
In this case, it seems to be fine. As the intend is just "not
allowing". Even it changes in the future, the worst case is just
"allowing", that fits your needs :)

It still won't affect what is working, so it is harmless side effect,
if it ever changes in the future.

Abuse or not doesn't matter so long it fits the needs, IMO.
 
B

bonono

Mike said:
That's *documented* as an implementation-dependent behavior. Using it
to get that effect is abuse of the feature, and may well quit working
in the future.
I don't see it mention as implementation-dependent behaviour on this
page, and this is supposed to be "(for language lawyers)".

http://www.python.org/doc/2.4.2/ref/slots.html

If it really is implementation detail but not a binding to the language
feature, I think someone need to change the doc, at least make it more
clear on this page.
 
B

bonono

Mike said:
And again, *what's the use case*? A number of people have asked why we
shouldn't allow this, but none of them been able to come up with a use
case better than "I think doing that is bad style."
oh, that is the usual argument anyway. It is nothing but style, most of
the time. Making a style thing into a right/wrong, better/worse
argument is really for time killing, there would never be real answer.
 
M

Mike Meyer

It still won't affect what is working, so it is harmless side effect,
if it ever changes in the future.

If not allowing is required, then allowing is "not working", pretty
much by definition.
Abuse or not doesn't matter so long it fits the needs, IMO.

I guess it doesn't matter to you whether or not your code works in the
future. It does to me.

<mike
 
B

bonono

Mike said:
If not allowing is required, then allowing is "not working", pretty
much by definition.


I guess it doesn't matter to you whether or not your code works in the
future. It does to me.
For this particular case, I don't see it as a problem.

First it meets the need, works on 2.2, 2.3, 2.4. No one knows what
would change in the future, beside the fact that I don't see it as
implementation-dependent in the doc.

Adding to the fact that things can/may be removed even it is fully
documented, without this caveat emptor(like the famous reduce/map
etc.). That is if I read the document when I was writing python codes
in the early days and used reduce/map/filter/lambda, my code still
breaks if in one day those are removed and I can't forsee that either.

So why bother, unless it has some definitive schedule(like saying in
2.5, reduce won't be there or __slot__ behaviour will definitely
change), then I would consider if the code I write now should avoid
using it.

And back to this case, I regard it as a no harm side effect, yes, it
doesn't perform as expected(assume it really is changed in some unknown
version in the future) that someone does hang something there, it
doesn't hurt my expected usage of the class, as this is used to prevent
unexpected usage.

Quoting the frequently used term "Practicality beats purity". If I have
a practical problem/needs now and it solves it, why not use it ?

The zip(it,it) case is even worse than this __slot__ situation and I
don't have much problem in using that either. It is not unusual in this
industry that certain software only expects to be working with some
combination of OS/Compiler version.
 
M

Mike Meyer

Quoting the frequently used term "Practicality beats purity". If I have
a practical problem/needs now and it solves it, why not use it ?

In other words, you have a use case. Cool. Please tell us what it is -
at least if it's better than "I think that's bad style."

<mike
 
B

bonono

Mike said:
In other words, you have a use case. Cool. Please tell us what it is -
at least if it's better than "I think that's bad style."
Huh ? I said that if I need "immutable instance", I won't hestiate to
use __slot__, I didn't say I have a need.

In other words, I see __slot__ as a solution to the problem, I am not
saying I have the problem in the first place. The "practicabilty beats
purity" is used to support this using __slot__ situation, not about the
need of immutable instance.
 
M

Mike Meyer

Huh ? I said that if I need "immutable instance", I won't hestiate to
use __slot__, I didn't say I have a need.

A need implies a use case. Yes, you didn't say you had one, you said
if, and I jumpted the gun.

Lots of people seem to want immutable instances. Nobody seems to have
a use case for them.

<mike
 
P

Paul Rubin

Mike Meyer said:
Lots of people seem to want immutable instances. Nobody seems to have
a use case for them.

What is the use case for immutable strings? Why shouldn't strings be
mutable like they are in Scheme?

Generally if I know I don't plan to mutate something, I'd want to make
it immutable so the runtime system can notice if I make an error.
It's like an "assert" statement spread through the whole program.
 
B

Ben Finney

Mike Meyer said:
Lots of people seem to want immutable instances. Nobody seems to
have a use case for them.

Perhaps you missed my release announcement of the 'enum' package that
explains why Enum instances are immutable.
 

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,774
Messages
2,569,599
Members
45,163
Latest member
Sasha15427
Top