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).
If the library code you depend on uses
isinstanceand relies on inheritance why not follow this route? If you cannot change the library then it is probably best to stay consistend with it.I also think that there are legitimate uses for
isinstance, and with the introduction of abstract base classes in 2.6 this has been officially acknowledged. There are situations whereisinstancereally is the right solution, as opposed to duck typing withhasattror using exceptions.Some dirty options if for some reason you really don’t want to use inheritance:
new.instancemethodyou create the wrapper methods for your instance, which then calls the original method defined in the original class. This seems to be the only option which neither modifies the original class nor defines new classes.If you can modify the class at runtime there are many options:
Use a runtime mixin, i.e. just add a class to the
__base__attribute of your class. But this is more used for adding specific functionality, not for indiscriminate wrapping where you don’t know what need to be wrapped.The options in Dave’s answer (class decorators in Python >= 2.6 or Metaclasses).
Edit: For your specific example I guess only the first option works. But I would still consider the alternative of creating a
LogFooor chosing an altogether different solution for something specific like logging.