mic said:
I've spent last hour trying to debug my code, when I found out that
instead of executing object's method I accidentaly overridden it with
variable (see below example). My stupid! But as the system becomes more
complex, I begun to wonder how to avoid such situations in the future.
Does anybody have some experiences about that?
Yes, and the experience is: it's not a problem in practice. Still,
if you disagree, Python gives you the tools to protect yourself -- read
on.
Simple example:
class test:
def a(self, value):
print value
t = test()
t.a('my test') <= returns obviously 'my test'
t.a = 'my test' <= creates object's attribute a, so I'm loosing my method
First of all, be sure to make your classes newstyle, e.g.
class test(object):
otherwise, by default, you get oldstyle classes, where compatibility
constraints mean most important new features just can't work right.
Now, limiting discussion to newstyle classes only:
in a class's dictionary there are two kinds of descriptors --
somewhat arbitrarily names 'data' and 'non-data' kinds of descriptors.
The 'non-data' kind, used for example for methods, are SPECIFICALLY
designed to be overridable per-instance. The 'data' kind aren't.
(Names are rather artificial; the real distinction is whether a
__set__ method is present in the descriptor -- if yes, it's data,
otherwise it's non-data -- calling it "setting-intercept" might
be clearer than calling it 'data'...!). Check it out...:
class c(object):
def a(self): pass
b = property(a)
descriptor 'a' is non-data, so it can be overridden in instances;
descriptor 'b' is data, so it cannot. E.g.:
Traceback (most recent call last):
The 'property' type of descriptors isn't quite suitable for what
you want, of course, because even just ACCESSING x.b makes a CALL
to x.a. Rather, you want a new and different "nonrebindablemethod"
type of descriptor whose __get__ is just like the function, while
its __set__ raises the exception. So, make such a type:
class norebind(object):
def __init__(self, func): self.func = func
def __get__(self, *args): return self.func.__get__(*args)
def __set__(self, *args): raise TypeError
class c(object):
def a(self): pass
b = norebind(a)
There -- now, after x=c(), attempts to rebind x.b will raise a
TypeError, as you apparently wish. Note that you can code the
class as:
class c(object):
def a(self): pass
a = norebind(a)
so that there's no x.b -- just a non-rebindable x.a.
You won't need it in practice, but it's still a cool hack
.
Alex