[Python-Dev] type(obj) vs. obj.class (original) (raw)

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