Search code examples
pythonexceptionobjectintrospection

In python is there a way to know if an object "implements an interface" before I pass it to a function?


I know this may sound like a stupid question, especially to someone who knows python's nature, but I was just wondering, is there a way to know if an object "implements an interface" so as to say?

To give an example of what I want to say:

let's say I have this function:

def get_counts(sequence):
     counts = {}
     for x in sequence:
         if x in counts:
             counts[x] += 1
         else:
             counts[x] = 1
     return counts

My question is: Is there a way to make sure that the object passed to the function is iterable? I know that in Java or C# I could do this by having the method accept any object that implements a specific interface, let's say (for example) iIterable like this: void get_counts(iIterable sequence)

My guess is that in Python I would have to employ preemptive introspection checks (in a decorator perhaps?) and throw a custom exception if the object doesn't have an __iter__ attribute). But is there a more pythonic way to do this?


Solution

  • Use polymorphism and duck-typing before isinstance() or interfaces

    You generally define what you want to do with your objects, then either use polymorphism to adjust how each object responds to what you want to do, or you use duck typing; test if the object at hand can do the thing you want to do in the first place. This is the invocation versus introspection trade-off, conventional wisdom states that invocation is preferable over introspection, but in Python, duck-typing is preferred over isinstance testing.

    So you need to work out why you need to filter on wether or not something is iterable in the first place; why do you need to know this? Just use a try: iter(object), except TypeError: # not iterable to test.

    Or perhaps you just need to throw an exception if whatever that was passed was not an iterable, as that would signal an error.

    ABCs

    With duck-typing, you may find that you have to test for multiple methods, and thus a isinstance() test may look a better option. In such cases, using a Abstract Base Class (ABC) could also be an option; using an ABC let's you 'paint' several different classes as being the right type for a given operation, for example. Using a ABC let's you focus on the tasks that need to be performed rather than the specific implementations used; you can have a Paintable ABC, a Printable ABC, etc.

    Zope interfaces and component architecture

    If you find your application is using an awful lot of ABCs or you keep having to add polymorphic methods to your classes to deal with various different situations, the next step is to consider using a full-blown component architecture, such as the Zope Component Architecture (ZCA).

    zope.interface interfaces are ABCs on steroids, especially when combined with the ZCA adapters. Interfaces document expected behaviour of a class:

    if IFrobnarIterable.providedBy(yourobject):
        # it'll support iteration and yield Frobnars.
    

    but it also let's you look up adapters; instead of putting all the behaviours for every use of shapes in your classes, you implement adapters to provide polymorphic behaviours for specific use-cases. You can adapt your objects to be printable, or iterable, or exportable to XML:

    class FrobnarsXMLExport(object):
        adapts(IFrobnarIterable)
        provides(IXMLExport)
    
        def __init__(self, frobnariterator):
            self.frobnars = frobnariterator
    
        def export(self):
            entries = []
            for frobnar in self.frobnars:
                entries.append(
                    u'<frobnar><width>{0}</width><height>{0}</height></frobnar>'.format(
                        frobnar.width, frobnar.height)
            return u''.join(entries)
    

    and your code merely has to look up adapters for each shape:

    for obj in setofobjects:
        self.result.append(IXMLExport(obj).export())