properties and formatting with self.__dict__

Andrew Bennetts andrew-pythonlist at puzzling.org
Sat Feb 22 14:43:58 CET 2003


On Sat, Feb 22, 2003 at 08:27:39AM +0000, Alex Martelli wrote:
> Andrew Bennetts wrote:
> 
> > I na?vely assumed that descriptors worked equally well for instances
> > as for classes, which turns out not to be true.  If it was, it would
> > indeed be fairly easy...
> 
> Hmmm, wouldn't such a change cause breakage elsewhere?

Almost certainly, yeah.  It'd make this particular insane use-case easier,
though ;)

> > So I was wrong -- you'd have to define custom metaclass, or something
> > along
> > those lines.  You could probably avoid a metaclass with something like:
> > 
> >     class C:
> >         def __init__(self, ...):
> >             instanceproperty(self, 'fullname', self.getfullname)
> > 
> > where instanceproperty was a function that inserted some sort of evil
> > descriptor into C.fullname if there wasn't already one... but it'd be
> > worse than simply defining __getattribute__ or __getattr__.
> 
> It wouldn't be a per-instance descriptor if it changed the class, though.
> It's either a way for instances to trip on each other (when you define
> the second istance ITS self.getfullname bound method gets called
> even for the already-existing first instance) or just a weird way to
> insert a normal-ish descriptor in a class, no?

You could probably make it check for the following cases:
    - there is already an evil fullname property installed, so just register
      with it
    - there is already an ordinary attribute 'fullname', so put an evil
      descriptor attribute there that knows to mimic the ordinary attribute
      for some instances

It's such a nasty idea that I'm not even going to try coding it...

> > Hmm... maybe you could abuse metaclasses to return a freshly minted class
> > object instead of a mere instance, so that descriptors would work....
> > 
> > [experiments]
> > 
[..code snipped..]
> > 
> > Which isn't too far removed from:
> > 
> >     class C:
> >         def __init__(self):
> >             self.fullname = property(self.getfullname)
> > 
> >         def getfullname(self):
> >             return 'Andrew Bennetts'
> > 
> > Well, you'd have to stand a few metres back from the monitor and squint,
> > but
> > then it kinda sorta looks similar <wink>.  Perhaps with a bit more
> > cleverness you could simplify my hack more.
> 
> It may help to coat the monitor with semi-opaque substances too;-).

Facing the other way probably helps even more :)

> As for simplification, one clever hack may be to notice that, since
> instanceIsClass only provides __new__ and C overrides it anyway,
> instanceIsClass is playing no role:

Hmm!  You're right...

> class C(object):
> 
>     def __new__(self):
>         self.fullname = property(self.getfullname)
>         return object.__new__(self)
> 
>     def getfullname(self):
>         return 'Andrew Bennetts'
> 
> x=C()
> 
> print x.fullname
> 
> 
> But of course the "self" in C.__new__ is C itself, so what we're
> doing is setting a property on the *class* C, not on any one of its
> instances.  We're setting it a bit late, and redundantly setting it
> again to a new instance of property with the same __get__ 
> (which calls unbound method C.getfullname), but it is in no
> sense a per-instance property...

Yeah.  That of course effectively boils down to just doing:

    class C(object):
        def getfullname(self):
            return 'Andrew Bennetts'
            
        fullname = property(getFullname)

which is pretty obviously not what we're after.

Instead, you'd have to resort to something like this:

    class instanceIsClass(object):
        def __new__(cls, *args, **kwargs):
            class Instance(cls): 
                def __new__(cls):
                    return object.__new__(cls)
            return Instance
    
    
    class C(instanceIsClass):
        def __new__(self, fn):
            o = instanceIsClass.__new__(self)
            o.fn = fn
            o.fullname = property(o.getfullname)
            print 'C.__new__ returning', repr(o)
            return o()
        def getfullname(self):
            return 'Full name: ' + self.fn
    
    c1 = C('Bozo the clown')
    c2 = C('Guido the PSU operative')
    
    print c1, ',', c2
    print c1.fullname, ',', c2.fullname

Again, I think this could probably be simplified by somehow combining the
two __new__s...

It's starting to look like it might not be worth the pain ;)

> > Thanks for helping me realise that I'd assumed wrongly :)
> 
> You're welcome -- this stuff IS subtle, so I really hoped I was the
> one that had misunderstood something (would hardly be the first
> time;-).  Perhaps more interestingly, can you think of a way to
> change object that WOULD let per-class properties work without
                               ^^^^^^^^^
                               I presume you mean per-instance?

> breaking something else?  I can't.  A custom metaclass that does
> allow per-instance properties could not be used universally, it
> seems to me.  But you may have thought more deeply about it...

No, I haven't thought about it deeply at all, as my mistakes so far
should've shown :)

I can't think of any reason off the top of my head why instance descriptors
would break anything, but I strongly suspect they would.  I also can't think
of a compelling use-case for per-instance descriptors off the top of my
head... between class descriptors, and __getattr__ and __getattribute__,
there's probably not much need to complicate things further.

-Andrew.






More information about the Python-list mailing list