design question: no new attributes

A

Alan Isaac

I have a class whose instances should only receive attribute
assignments for attributes that were created at inititialization.
If slots are not appropriate, what is the Pythonic design for this?

Thanks,
Alan Isaac
 
L

Larry Bates

Alan said:
I have a class whose instances should only receive attribute
assignments for attributes that were created at inititialization.
If slots are not appropriate, what is the Pythonic design for this?

Thanks,
Alan Isaac

My understanding of "Pythonic design" is not to worry about it.
If users want to set attributes that won't accomplish anything
productive (within your class) let them.

-Larry
 
B

Ben Finney

Alan Isaac said:
I have a class whose instances should only receive attribute
assignments for attributes that were created at inititialization.
If slots are not appropriate, what is the Pythonic design for this?

The Pythonic design is: don't expect to have such control over users
of your code. (I encountered this in trying to design an Enum class,
with enumeration members as attributes.)

Document, in the class doc string and anywhere else you feel
appropriate, the fact that attributes shouldn't be added, and let your
users deal with their own bugs if they ignore that.
 
A

Alan Isaac

Ben Finney said:
The Pythonic design is: don't expect to have such control over users
of your code.

I know this is a popular response,
but the fact of the matter remains that
it can be helpful to know when someone
tries to set a value for a nonexisting attribute.
This is especially true if there have been any
interface changes.

So my question remains:
how best to trap any such attempt
subsequent to object initialization?
(I am willing to have properties for
all data attributes, if that helps.)

Thank you,
Alan Isaac
 
S

Steven D'Aprano

I know this is a popular response,

It's popular for good reason.

but the fact of the matter remains that
it can be helpful to know when someone
tries to set a value for a nonexisting attribute.

I'm afraid your understanding of the word "fact" is different from my
understanding of the word "fact".

But be that as it may, if you wish to waste^H^H^H^H^H spend time
trying to prevent people from adding attributes to their instances,
Just Because, you can do something like this:

class Difficult(object):
def __setattr__(self, name, value):
if self.__dict__.has_key(name):
print "'%s' exists as an instance attribute" % name
self.__dict__[name] = value
elif self.__class__.__dict__.has_key(name):
print "'%s' exists as a class attribute" % name
self.__class__.__dict__[name] = value
else:
print "Can't create new attributes, 'cos I said so!"


There ought to be a name for that anti-pattern of molly-coddling,
bondage-and-domination philosophy of "you're only allowed to use my class
the way I want you to use it".

(Excuse my cynicism, for all I know you've got a really good reason for
wanting to do this, perhaps as part of a cold-fusion machine.)
 
A

Arnaud Delobelle

So my question remains:
how best to trap any such attempt
subsequent to object initialization?
(I am willing to have properties for
all data attributes, if that helps.)

You can define the __setattr__ method in your class as

def __setattr__(self, attr, val):
if hasattr(self, attr):
self.__dict__[attr] = val
else:
# Tell the user off

but of course the user can set attributes through
instance.__dict__['attrname'] = val

So to really do it you would need to design your own metaclass
 
B

Ben Finney

Alan Isaac said:
I know this is a popular response, but the fact of the matter
remains that it can be helpful to know when someone tries to set a
value for a nonexisting attribute.

That's quite a different request from your original request asking the
Pythonic design for *preventing* setting of new attributes.
This is especially true if there have been any interface changes.

If the interface has changed, then you don't need to trap *every*
setting of an attribute; only the names that you know have changed.

===== foo_v1.py =====

class Foo(object):
def bar(self):
return 17

spam = 42
=====

===== foo_v2.py =====

import logging

class Foo(object):
def baz(self):
return 13

eggs = 69

def bar(self):
""" Obsolete interface for Foo.baz() """
logging.warn("Deprecated Foo.bar() call, please use Foo.baz()")
return self.baz()

def _get_spam(self):
""" Obsolete interface for Foo.eggs """
logging.warn("Deprecated Foo.spam reference, please use Foo.eggs")
return self.eggs
def _set_spam(self, value):
""" Obsolete interface for Foo.eggs """
logging.warn("Deprecated Foo.spam reference, please use Foo.eggs")
self.eggs = value
spam = property(_get_spam, _set_spam)
=====

After a reasonable grace period, you can drop these compatibility
interfaces in a future version.
 
D

Dennis Lee Bieber

(Excuse my cynicism, for all I know you've got a really good reason for
wanting to do this, perhaps as part of a cold-fusion machine.)

Well... If it IS a cold-fusion machine, I'd recommend using Ada to
control it... might be more certifiable that way...
--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/
 
A

Alan Isaac

Arnaud Delobelle said:
def __setattr__(self, attr, val):
if hasattr(self, attr):
self.__dict__[attr] = val
else:
# Tell the user off

But then you cannot even set attributes during initialization, right?
I want that restriction only after an object is initialized.
(I realize that I could condition on an attribute set during initialization,
but I am asking for the most elegant way to achieve this.)

Alan
 
A

Alan Isaac

class Difficult(object):
def __setattr__(self, name, value):
if self.__dict__.has_key(name):
print "'%s' exists as an instance attribute" % name
self.__dict__[name] = value
elif self.__class__.__dict__.has_key(name):
print "'%s' exists as a class attribute" % name
self.__class__.__dict__[name] = value
else:
print "Can't create new attributes, 'cos I said so!"



But this prevents setting attributes during initialization,
so it does not meet the spec.
Cheers,
Alan
 
C

Chris Mellon

Arnaud Delobelle said:
def __setattr__(self, attr, val):
if hasattr(self, attr):
self.__dict__[attr] = val
else:
# Tell the user off

But then you cannot even set attributes during initialization, right?
I want that restriction only after an object is initialized.
(I realize that I could condition on an attribute set during initialization,
but I am asking for the most elegant way to achieve this.)

Alan

You specifically excluded slots in your spec, but why? It's as close
to a working, elegant solution you will find. Nothing else will work,
and will be horribly inelegant.

You can still do it the above way, with nasty hacking (but not really
any nastier than what you're doing) by pushing the __setattr__ hook on
as the last part of your __init__. Of course, if they subclass and
don't call your base class __init__, then they can get by you. So
maybe you should inspect the call stack in your __setattr__ and only
allow instance setting if you're within your "approved" __init__
method. Even then, you can't stop them from grabbing your class,
removing or overwriting the hook, and creating classes on the fly.

I think at this point (actually quite a while before this point)
you're getting into silly amounts of thinking about it. Python for
consenting adults and all that.
 
A

Alan Isaac

OK, let me approach this from a new direction.
Suppose I define a class that behaves I think
as I stipulated::

class NothingNew:
a = 0
def __init__(self):
self.b = 1
self.initialized = True
def __setattr__(self, attr, val):
if not hasattr(self,'initialized') or hasattr(self, attr):
self.__dict__[attr] = val
else:
raise ValueError

After adding some documentation,
what are the downsides? (Assume a small
project where everyone knows this has been
done to the NothingNew class.) Anyone who wants the
behavior can keep it. Anyone who wants to add
stuff to an object just has to delete its
"initialized" attribute. Anyone who want other
behavior defined in this or a derived class can
just override __init__. Does this address most
of the (oddly passionate) objections?

Thanks,
Alan Isaac
 
B

Ben Finney

Alan Isaac said:
OK, let me approach this from a new direction. Suppose I define a
class that behaves I think as I stipulated::

class NothingNew:
a = 0
def __init__(self):
self.b = 1
self.initialized = True
def __setattr__(self, attr, val):
if not hasattr(self,'initialized') or hasattr(self, attr):
self.__dict__[attr] = val
else:
raise ValueError

After adding some documentation, what are the downsides?

The biggest downside I see is that you're going through contortions to
solve a problem you haven't demonstrated actually exists.

A smaller problem is that the ValueError should describe what's going wrong:

raise ValueError("The author of this class thinks you're up to no good")

Really, though, adding attributes to an instance is a normal thing to
do in Python, and you've not yet shown why you want that normal
functionality to be special-cased here.
 
S

Steven D'Aprano

class Difficult(object):
def __setattr__(self, name, value):
if self.__dict__.has_key(name):
print "'%s' exists as an instance attribute" % name
self.__dict__[name] = value
elif self.__class__.__dict__.has_key(name):
print "'%s' exists as a class attribute" % name
self.__class__.__dict__[name] = value
else:
print "Can't create new attributes, 'cos I said so!"



But this prevents setting attributes during initialization,
so it does not meet the spec.

What, you expect us to do everything for you? *wink*

If you want the class to change behaviour after initialisation, you have
to code it to do so. The easy, but inelegant, way is to set a flag.

Finding an elegant way to do so is a little like asking for an elegant way
to scrub a septic tank clean. But one way might be to change the class
after initialisation:

class Parrot(object):
def __init__(self, data):
self.data = data
self.__class__ = Annoying

class Annoying(Parrot):
def __setattr__(self, name, value):
print "Annoy the user."
5
 
A

Alan Isaac

Ben said:
Really, though, adding attributes to an instance is a normal thing to
do in Python, and you've not yet shown why you want that normal
functionality to be special-cased here.


I accept your earlier point that if an interface changes
one can just trap use of the old interface. But I remain
interested in preventing dynamic attribute creation in
particular cases.

I have not tried to argue that dynamic attribute creation
can be dangerous for a couple reasons.
- There is no reason to expect this list to be receptive.
- I do not have the experience to make a general argument.
- I do not always find it a problem, but only in certain situations.

However I will observe that
- entire languages are structured on the premise that dynamic
attribute creation can be hazardous
- debuggers watch out for dynamic attribute creation, which
tells us it is a common source of bugs
- I sincerely doubt that anyone who has written more than
a couple scripts in Python has never accidentally created an
attribute dynamically while intending to assign to an existing
attribute.

I know the response: write good unit tests. OK, but right now
I am writing code where this restriction will serve as a reasonable
error check, and the design I offered allows very easy removal
of the restriction in the future. Say, once I have written adequate
unit tests.

Alan
 
D

Diez B. Roggisch

However I will observe that
- entire languages are structured on the premise that dynamic
attribute creation can be hazardous

Yup, and you are free to use one of them. And as an additional benefit, they
will be more performant because you then can optimize the code further.

But they certainly do need more code to accomplish the same things easily
done in python (or other dynamic languages for that matter)
- debuggers watch out for dynamic attribute creation, which
tells us it is a common source of bugs
- I sincerely doubt that anyone who has written more than
a couple scripts in Python has never accidentally created an
attribute dynamically while intending to assign to an existing
attribute.

Certainly that happened, but not in a number of occasions that its impact on
every day programming isn't dwarfed by the much more prevalent gain in
productivity using python.

You strive for the combination of disadvantages of statically typed
languages with the disadvantages of a dynamically typed one.

Not too much of an convincing goal IMHO.

Diez
 
B

Bruno Desthuilliers

Alan Isaac a écrit :
I accept your earlier point that if an interface changes
one can just trap use of the old interface. But I remain
interested in preventing dynamic attribute creation in
particular cases.

I have not tried to argue that dynamic attribute creation
can be dangerous for a couple reasons.
- There is no reason to expect this list to be receptive.

Right. Don't you wonder why ?
- I do not have the experience to make a general argument.
- I do not always find it a problem, but only in certain situations.

Which ones ?
However I will observe that
- entire languages are structured on the premise that dynamic
attribute creation can be hazardous

Entire languages are structured on the premise that developers are dumb
and that anything dynamic can be hazardous.

Seriously, if you fear dynamic attribute creation, you'd better choose
another language. What about dynamically changing the class of an
object? Now ain't *that* hazardous ?-)
- debuggers watch out for dynamic attribute creation,

Which ones ?
which
tells us it is a common source of bugs
s/common/potential/

- I sincerely doubt that anyone who has written more than
a couple scripts in Python has never accidentally created an
attribute dynamically while intending to assign to an existing
attribute.

Of course. But MHE (7+ years of Python programming - more than a couple
scripts...) is that this has not proven so far to be a common and
annoying enough problem to justify fighting against the language.
I know the response: write good unit tests. OK, but right now
I am writing code where this restriction will serve as a reasonable
error check,

I don't share your definition of "reasonable". But you should have
guessed by now !-)
 
G

Gabriel Genellina

However I will observe that
- entire languages are structured on the premise that dynamic
attribute creation can be hazardous

That's why we have so many languages to choose from. What is highly
important for someone is irrelevant for others. [PL/1 was destined to be
The Programming Language...]
If you actually dislike the concept of dynamic attribute creation, you're
using the wrong language.
- debuggers watch out for dynamic attribute creation, which
tells us it is a common source of bugs

Which debugger, please? Might be interesting. (As a debugging tool, not as
a design technique).
- I sincerely doubt that anyone who has written more than
a couple scripts in Python has never accidentally created an
attribute dynamically while intending to assign to an existing
attribute.

That *might* happen, but not so frequently as to fight against the dynamic
nature of Python.
I know the response: write good unit tests. OK, but right now
I am writing code where this restriction will serve as a reasonable
error check, and the design I offered allows very easy removal
of the restriction in the future. Say, once I have written adequate
unit tests.

If it suits your needs, fine! But you'll find that most Python programmers
won't agree with your approach.
 
G

greg

Alan said:
class NothingNew:
a = 0
def __init__(self):
self.b = 1
self.initialized = True

There's a problem with that when you want to subclass:

class NothingElseNew(NothingNew):

def __init__(self):
NothingNew.__init__(self)
self.c = 42 # <--- Not allowed!

You could get around this by temporarily de-initializing
it, i.e.

def __init__(self):
NothingNew.__init__(self)
del self.__dict__['initialized']
self.c = 42
self.initialized = True

but that's not very elegant.
 

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
474,432
Messages
2,571,682
Members
48,796
Latest member
Greg L.

Latest Threads

Top