A Python "Cast Constructor"

Very occasionally, I write code where I’m given an object of some class (usually from a library call), but I wish to use additional methods on that class as though they had been defined there. In some languages (Objective-C shines in this regard with categories), you can do this very naturally. In Python, most people probably resort to monkey patching to accomplish this.

This is Probably An Anti-Pattern

I want to preface this with an admission that I have no idea if this will generally work (please comment if you know of a reason why it won’t!), and that I suspect that this is a horrible idea. And yet, here we go:

import copy

class Base(object):
    def __init__(self):
        self.base_init = True

    def foo(self):
        return 'base foo'

    def bar(self):
        return 'base bar'

class Child(Base):
    def __new__(cls, other):
        if isinstance(other, Base):
            other = copy.copy(other)
            other.__class__ = Child
            return other
        return object.__new__(cls)

    def __init__(self, other):
        self.child_init = True

    def bar(self):
        return 'child bar'

b = Base()
assert b.base_init == True
assert b.foo() == 'base foo'
assert b.bar() == 'base bar'
assert b.__class__ == Base

c = Child(b)
assert c.base_init == True
assert c.child_init == True
assert c.foo() == 'base foo'
assert c.bar() == 'child bar'
assert c.__class__ == Child
assert b.__class__ == Base

Have I made some glaring mistake? Is there something obviously wrong with this, other than the abuse of the language inherent in trying to do such a thing?

Edit: Thanks to Jesse Davis for the suggestion to use copy.copy. Also see the Stackoverflow question which inspired this post.

python, anti-pattern

Your thoughts:

FWIW, C++ enshrines this sort of behavior with a specific cast operator, static_cast. “static_cast can perform conversions between pointers to related classes, not only from the derived class to its base, but also from a base class to its derived.“ (http://www.cplusplus.com/doc/tutorial/typecasting/)

— Mark Weiss, 2012-04-05 03:30 pm

Your new doesn’t quite do what you think when “other” is not an instance of Base.

new is a class method, so the first argument is not “self”, but the class. Instead of just returning it you should do:

return object.__new__(cls)
— Michael Foord, 2012-04-05 03:53 pm

There may be problems when copy is expensive (or even impossible for some reason), or has some undesirable side effect.

An alternative is to create a class that stores a reference to the wrapped object, and uses __getattr__ to proxy everything to the wrapped object. This probably has a slightly greater performance penalty, and maybe some other gotchas. Also super() will have to be replaced with explicit delegation.

— Luke Plant, 2012-04-05 03:59 pm

@Michael Foord — thanks, I haven’t written a __new__ in a while. Updated.

— Dan Crosta, 2012-04-05 04:33 pm

@Michael Foord – new is actually a special-cased static method which is provided the class object as it’s first argument by the metatype’s call method.

In [8]: class Foo(object):
   ...:     @staticmethod
   ...:     def static():
   ...:         pass
   ...:     def __new__(cls):
   ...:         pass
   ...:     def __init__(self):
   ...:         pass

In [9]: Foo.static
Out[9]: function __main__.static

In [10]: Foo.__new__
Out[10]: function __main__.__new__

In [11]: Foo.__init__
Out[11]: unbound method Foo.__init__
— Chris Colbert, 2012-04-05 10:34 pm

Is it possible that your goal could be accomplished cleanly with the http://en.wikipedia.org/wiki/Adapter_pattern instead? In almost 15 years of Python programming I have never, ever had to do anything like what you are doing. :) Could you explain a little more about what you are trying to accomplish?

— Brandon Rhodes, 2012-04-06 09:23 am

@Brandon Rhodes Yes, that’s certainly an option. I am not so much “trying to accomplish” something as just experimenting to see what’s possible. I wouldn’t write code like this in a production system, but possibly in some experimental code or toy/personal projects.

One advantage of this approach over Adapter, though, is that issubclass(instance, Base) will still work as expected — the class is, in fact, an instance of Base (by way of inheritance; its concrete type is Child in this example).

— Dan Crosta, 2012-04-06 10:24 am

You can actually directly rewrite the bar function both at a class and an instance level. Details here.

— Wes Chow, 2012-04-16 11:25 am

Comments are closed.