properties and formatting with self.__dict__

Andrew Bennetts andrew-pythonlist at puzzling.org
Sat Feb 22 12:30:20 EST 2003


On Sat, Feb 22, 2003 at 04:09:06PM +0000, Alex Martelli wrote:
> Andrew Bennetts wrote:
>    ...
> > 
> > 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
> 
> OK -- this is just a function that, given a class X, returns
> a subclass of X with a __new__ overridden to bypass X.__new__
> and go right to object.__new__.  Said function is well masked
> up with false moustache and everything but we can clearly
> code it a bit more simply (avoiding the possibly confusing
> use of cls for different things in different scopes) as:
> 
> def subclassWithoutFancyNew(classWithFancyNew):
>     class TheSubclass(classWithFancyNew):
>         __new__ = object.__new__
>     return TheSubclass

Yeah, that's alot saner.

> Note to self: nice simple cookbook-worthy technique to
> "remove" a special method from a given instance of a class:
> subclass it on the fly, overriding said special method
> to go back to a suitable ancestor class or the like.  See,
> I _knew_ this thread would be instructive to me.

That's a relief... I was starting to doubt this thread was in any way
productive :)

> >     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()
> 
> I don't think we gain anything by subclassing C from
> instanceIsClass.  Rather, we simply need to call the
> above function, or otherwise provide such subclassing.

You're right... I was attempting to keep as much evil in the superclass as
possible, on the basis that it would mean the subclasses could be relatively
clean, but unfortunately I couldn't really make it work.

> It may be cleared to use cls as the first argument to
> __new__, as is normally done, rather than self, and
> do the fine subclassing inline, a la:
> 
> class C(object):
> 
>     def __new__(cls, thefullname):
>         class TheSubclass(cls):
>             __new__ = object.__new__
>             fn = thefullname
>             fullname = property(cls.getfullname)
>         return TheSubclass()
> 
>     def getfullname(self):
>         return 'Full name: ' + self.fn
> 
> 
> Nice!  I think we're sinking our teeth into something
> potentially useful, albeit arguably somewhat blackly
> magickal: when we see something can be done only at
> CLASS-level and we want to do it an INSTANCE-level,
> one trick that can work is to *make for each instance
> its own class*, a dedicated subclass of what appears
> to be "the class" we're designing.  We can do that
> without custom metaclasses in theclass.__new__, which
> can generate the "custom subclass" on the fly (with
> the "__new__=object.__new__" little trick to avoid
> recursion;-), prep it up as needed (here we're doing
> nothing fancy so we may as well do everything in the
> body of the custom subclass), and finally instantiate
> it and return the freshly minted instance.  Cool!

Yeah, that is nice.  One of my favourite tricks in Python has been creating
little subclasses dynamically like this, and it's good to see it works well
for this case too :)

I find the similarities (and differences) between subclassing and
instantiating interesting -- if you squint, they could almost be the same
concept ;)

Things like Zope's acquisition and even Twisted's adapters (which were
inspired by Zope 3, I think) are also similar; objects that delegate much of
their behaviour to other objects.  Sometimes 

> I like this because it seems to me to give a little
> bit of the flavour and usefulness of prototype-based
> languages, even though Python is class/instance-based.

What do you mean by prototype-based languages?

> By giving each instance its own custom subclass, we
> can think we're basically making a "prototype" that is
> the package instance+customsubclass: it can be changed
> safely without impacting anything else (in particular
> such changes won't impact other instances of the same
> "mother class") and such changes afford all of the
> customization that either instances OR classes afford.

Yes.  It blurs the distinction between classes and instances even more...
they are already a bit fuzzy, considering that we have metaclasses, whose
instances are classes...

> Now even quite apart from properties this DOES have
> potential uses!  Remember how in the classic object
> model we COULD once in a while redefine a per-instance
> special method, say __str__, and make that one and only
> instance special (in the way it stringifies, for example)
> without affecting all other instances of the same class?

To be perfectly honest, I hadn't realised that new-style classes had taken
that away, although given what I've learnt in this thread plus what I
already knew about slots and the restrictions on changing __class__, I can't
say I'm surprised.

> With the new-style object model we can't do that any
> more (because, in an important step of conceptual clarity,
> now special methods are only looked up in the class, not
> in the instance) -- BUT, for that one case in a zillion
> where such an approach IS really what we want, all we
> need to do is adopt the "give each instance its own
> custom subclass" (idiom? pattern? hallucination...?),

Let's just call it a nervous tic ;)

> and, voila, we can do "quasi-per-instance" customization
> again (we'll actually customize the SUBCLASS of each
> instance we need to tweak, but that's OK as it's a custom
> subclass anyway).
> 
> Since an instance's __class__ attribute IS dynamically
> assignable we could even have a dynamic variation of
> this -- but I won't push the idea any further, as it
> does seem to be out on a limb already.  NEAT though...

I'm curious to know what you were thinking of here.

> And surely this CAN usefully be packaged into a custom
> metaclass -- WITHOUT the kind of overhead I had in
> mind at first (customizing __getattribute__, eek) but
> just with a tiny little effort at instantiation time
> (and THAT is generally quite affordable overhead).  But
> that will wait, as it's not too hard anyway.

Hah!  That's a bold claim, considering what happened when I claimed
something was "fairly easy".  :D

> >     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...
> 
> Agreed, see above.

Indeed.  You are a marvellous clarifier :)

> > It's starting to look like it might not be worth the pain ;)
> 
> Perhaps not specifically for "per-instance properties", but
> I _do_ think the whole approach has some merit in general.

This is a result I wasn't expecting from this thread.

> > 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.
> 
> I think we already have all the complication we need, thanks
> to the nifty "custom subclass per instance" approach we did
> finally unearth here;-).  Quite close to some similar tricks
> in the cookbook, if you step back and squint;-).  Well, in
> spirit, anyway...

I think I'll have to squint doubly-hard next time I post code to c.l.p :)

-Andrew.






More information about the Python-list mailing list