In Python, I've seen the recommendation to use holding or wrapping to extend the functionality of an object or class, rather than inheritance. In particular, I think that Alex Martelli spoke about this in his Python Design Patterns talk. I've seen this pattern used in libraries for dependency injection, like pycontainer.
One problem that I've run into is that when I have to interface with code that uses the
isinstance anti-pattern, this pattern fails because the holding/wrapping object fails the isinstance
test. How can I set up the holding/wrapping object to get around unnecessary type checking? Can this be done generically? In some sense, I need something for class instances analogous to signature-preserving function decorators (e.g., simple_decorator or Michele Simionato's decorator).
A qualification: I'm not asserting that all isinstance
usage is inappropriate; several answers make good points about this. That said, it should be recognized that isinstance
usage poses significant limitations on object interactions---it forces inheritance to be the source of polymorphism, rather than behavior.
There seems to be some confusion about exactly how/why this is a problem, so let me provide a simple example (broadly lifted from pycontainer). Let's say we have a class Foo, as well as a FooFactory. For the sake of the example, assume we want to be able to instantiate Foo objects that log every function call, or don't---think AOP. Further, we want to do this without modifying the Foo class/source in any way (e.g., we may actually be implementing a generic factory that can add logging ability to any class instance on the fly). A first stab at this might be:
class Foo(object):
def bar():
print 'We\'re out of Red Leicester.'
class LogWrapped(object):
def __init__(self, wrapped):
self.wrapped = wrapped
def __getattr__(self, name):
attr = getattr(self.wrapped, name)
if not callable(attr):
return attr
else:
def fun(*args, **kwargs):
print 'Calling ', name
attr(*args, **kwargs)
print 'Called ', name
return fun
class FooFactory(object):
def get_foo(with_logging = False):
if not with_logging:
return Foo()
else:
return LogWrapped(Foo())
foo_fact = FooFactory()
my_foo = foo_fact.get_foo(True)
isinstance(my_foo, Foo) # False!
There are may reasons why you might want to do things exactly this way (use decorators instead, etc.) but keep in mind:
- We don't want to touch the Foo class. Assume we're writing framework code that could be used by clients we don't know about yet.
- The point is to return an object that is essentially a Foo, but with added functionality. It should appear to be a Foo---as much as possible---to any other client code expecting a Foo. Hence the desire to work around
isinstance
. - Yes, I know that I don't need the factory class (preemptively defending myself here).