too dynamic - how to avoid?

Alex Martelli aleax at aleax.it
Thu Oct 2 06:20:46 EDT 2003


mic wrote:

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

>>> x=c()
>>> x.a=23
>>> x.b=23
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: can't set attribute
>>>

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





More information about the Python-list mailing list