properties and formatting with self.__dict__

Alex Martelli aleax at aleax.it
Sat Feb 22 11:09:06 EST 2003


Andrew Bennetts wrote:
   ...
> 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...

Indeed, I'm seriously tempted to redirect the thread to alt.evil.
BUT -- I like where we're getting, below, so I won't...;-).


> Yeah.  That of course effectively boils down to just doing:
> 
>     class C(object):
>         def getfullname(self):
>             return 'Andrew Bennetts'
>             
>         fullname = property(getFullname)

Exactly, except that you get to mention getfullname (all lower
case -- Python IS case-sensitive;-) "lexically before" you
define it (because you only mention it within __new__'s body;-).


> 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

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

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.


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


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.

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.


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?

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...?),
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...


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.


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

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


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


Alex





More information about the Python-list mailing list