Question about accessing class-attributes.

Alex Martelli aleax at aleax.it
Wed Apr 30 04:01:05 EDT 2003


On Wednesday 30 April 2003 02:15 am, Bjorn Pettersen wrote:
   ...
> > is probably some sound implementation reason for the current
> > behavior, but if so it should be better documented, I think.
>
> SF 729913. I have not been able to find an exact description of the
> lookup rules for new-style classes anywhere.

Good point.  I guess the language reference DOES need to provide
"language lawyers" with those!


> > Yes, of course not -- super(MyClass, self) is NOT an instance
> > of MyClass (isn't THAT why you're calling super...?).
>
> I'm also missing an exact description of what super() returns. It looks
> like it's an object that when you call method 'm', invokes the proper
> super class' method 'm', and if that 'm' invokes any otherm method,
> 'm2', the original objects definiton 'm2' is used. So it looks like it

Again, this needs to go somewhere in the docs, but the effect of

super(aclass, aninst).amethod()

SHOULD be the same as anotherclass.amethod(aninst) where
anotherclass is the first class following aclass in the mro of aninst's
class that defines amethod.  Note that the *lookup* for amethod
CANNOT use aclass itself, BUT the argument PASSED to amethod
MUST be aninst -- or else how would super enable "cooperative
superclass method calling"?

Given
    sup = super(aclass, aninst)
if sup were an instance of aclass, then the lookup for sup.amethod
would start in aclass, thus the typical use case such as:
    def amethod(self):
        print 'before'
        super(thisclass, self).amethod()
        print 'after'
would just recurse endlessly (well, until that raises an exception;-)
making it pretty useless.  But also, unless aninst were the instance
passed to anotherclass.amethod, this too would inhibit the intended
effect -- i.e. in the typical diamond inheritance structure:

class base(object):
    def amethod(self):
        print 'base.amethod'

class der1(base):
    def amethod(self):
        print 'der1.amethod'
        super(der1, self).amethod()

class der2(base):
    def amethod(self):
        print 'der2.amethod'
        super(der2, self).amethod()

class der12(der1, der2): pass

class der21(der2, der1): pass

der12().amethod()
der21().amethod()

how would we obtain the required sequence of prints in both
cases?

I'm NOT claiming this is perfectly stabilized yet nor that all
subtle bugs have yet been shaken off -- I'm just trying to
explain why _I_ don't find this behavior at all surprising.


> should be substitutable, or at least a first class object, but as you

Well it IS a first-class object, surely?  You can pass it as an argument,
return it as a function's result, use it as a value in a list or dict -- 
aren't these the characteristics DEFINING "first-class object"?

> said: "of course not". Am I missing some documentation, or is it
> unreasonable of me to think it should work this way (what way should it
> work... exactly)?

I wouldn't be surprised to find out that the "language lawyer" levels
of docs haven't been written down anywhere yet -- i.e. that there
is nothing much beyond descrintro or the short paragraph under
http://www.python.org/dev/doc/devel/lib/built-in-funcs.html that
describes super.  But as for 'reasonableness', I dunno -- I do not
understand how you would expect 'super' to work to enable its basic
use case of cooperative superclass method calling and yet NOT have

super(aclass, aninst).amethod()

behave just the same as

anotherclass.amethod(aninst)


There may be a good design idea hiding in your expectations -- as
long as it doesn't break super's fundamental use case -- and so I
am quite keen on understanding them!  Indeed if we were able to
classify the current behavior as "a bug" in some respect, AND give
a patch that breaks no currently designed-in behavior, yet "fixes the
bug" and makes super more useful to you, then that's the kind of
fix that COULD go in 2.3 beta 2 (new functionality won't, but in
some sense it might be considered "a bug" if super violates the
normal expectations of clearly-advanced users such as you).

Maybe we should break this thread in two -- one about metaclasses
and one about super -- with suitable subjects... I do not understand
where the 'bugs' of the two may be related nor actually what they
really have to do with "accessing class-attributes" except in the
vaguest, broadest sense.


> > > Perhaps a more realistic exmaple?:
> > >
> > >   class Secure(object):
> > >     def __init__(self, name):
> > >       self.name = name
> > >
> > >     def __getattr__(self, attr):
> > >       return getSecureMethod(self.name, attr, getUserPrivileges())
> > >
> > >   class LogFoo(Secure):
> > >     def __init__(self):
> > >       Secure.__init__(self, 'SalaryDB')
> > >
> > >     def foo(self):
> > >       print 'calling foo'
> > >       return super(LogFoo, self).foo()
> > >
> > >   lf = LogFoo()
> > >   print lf.foo()
> >
> > Sorry, I don't get it.  How is this related to metaclasses...?
>
> Not terribly obvious was it... :-)  And it's a bad example too. The
> thinking was that if Super's metaclass could intercept special methods
> through it's __getattr__, the object returned from super() could look
> more like a regular object.  That Super.__getattr__ doesn't check for a
> __getattr__ in the __mro__ chain is a problem, but a separate one (sorry
> for the confustion).

"the object returned from super" DOES look a lot "like a regular
object", except it has a custom metaclass that ensures its fundamental
behavior -- attribute lookups starts AFTER the given class, but, if and
when any bound-methods are built, the instance they're bound to is
the given one.  We can't break THAT, I think.

I think that, currently, if you want the functionality you seem to be
after in this example, you must ensure that superclass Secure has,
in its class dict at the time type.new is called to build it, explicit
entries for all specialmethods of your interest -- that way, the slots
will be filled in (perhaps with special-purpose descriptors that do
the just-in-time lookup you may want).  But, for methods that you
look up explicitly, a metaclass's __getattr__ (NOT of course the
__getattr__ of the CLASS on which you're looking up -- that only
applies to lookups on INSTANCES of that class) should work fine.

Yes, maybe we should separate super-issues and metaclass-issues
and find a sharp example in each case of where reasonably expected
behavior from docs, and observed behavior in 2.3 beta 1, differ.  I
think it does not need to be a "reasonable use case": that would be
needed if we were arguing for EXTRA functionality of course -- but
then it would go in 2.4 so there's no hurry, to say the least.  If we're
arguing there are BUGS, so the fixes could go in 2.3, then just a
way to show up each bug clearly should suffice (hopefully).


Alex






More information about the Python-list mailing list