[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