[Python-Dev] type(obj) vs. obj.__class__

Steven D'Aprano steve at pearwood.info
Sun Oct 18 21:47:31 EDT 2015


On Sun, Oct 18, 2015 at 05:35:14PM -0700, David Mertz wrote:

> In any case, redefining a method in a certain situation feels a lot less
> magic to me than redefining .__class__

That surprises me greatly. As published in the Python Cookbook[1], there 
is a one-to-one correspondence between the methods used by an object and 
its class. If you want to know what instance.spam() method does, you 
look at the class type(instance) or instance.__class__, and read the 
source code for spam.

With your suggestion of re-defining the methods on the fly, you no 
longer have that simple relationship. If you want to know what 
instance.spam() method does, first you have to work out what it actually 
is, which may not be that easy. In the worst case, it might not be 
possible at all:

class K:
    def method(self):
        if condition:
            self.method = random.choice([lambda self: ..., 
                                         lambda self: ...,
                                         lambda self: ...])


Okay, that's an extreme example, and one can write bad code using any 
technique. But even with a relatively straight-forward version:

    def method(self):
        if condition:
            self.method = self.other_method


I would classify "change the methods on the fly" as self-modifying code, 
which strikes me as much more hacky and hard to maintain than something 
as simple as changing the __class__ on the fly. 

Changing the __class__ is just a straight-forward metamorphosis: what 
was a caterpillar, calling methods defined in the Caterpillar class, is 
now a butterfly, calling methods defined in the Butterfly class. 

(The only change I would make from the published recipe would be to make 
the full Ringbuffer a subclass of the regular one, so isinstance() tests 
would work as expected. But given that the recipe pre-dates the 
wide-spread use of isinstance, the author can be forgiven for not 
thinking of that.)

If changing the class on the fly is a metamorphosis, then it seems to me 
that self-modifying methods are like something from The Fly, where a 
horrible teleporter accident grafts body parts and DNA from one object 
into another object... or at least *repurposes* existing methods, so 
that what was your leg is now your arm.

I've done that, and found it harder to reason about than the 
alternative:

"okay, the object is an RingBuffer, but is the append method the 
RingBuffer.append method or the RingBuffer.full_append method?"

versus

"okay, the object is a RingBuffer, therefore the append method is the 
RingBuffer.append method".


In my opinion, the only tricky thing about the metamorphosis tactic is 
that:

obj = Caterpillar()
# later
assert type(obj) is Caterpillar

may fail. You need a runtime introspection to see what the type of obj 
actually is. But that's not exactly unusual: if you consider Caterpillar 
to be a function rather than a class constructor (a factory perhaps?), 
then it's not that surprising that you can't know what *specific* 
type a function returns until runtime. There are many functions with 
polymorphic return types.





[1] The first edition of the Cookbook was edited by Python luminaries 
Alex Martelli and David Ascher, so this recipe has their stamp of 
approval. This isn't some dirty hack.


-- 
Steve


More information about the Python-Dev mailing list