14

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?

NlightNFotis
  • 9,559
  • 5
  • 43
  • 66

4 Answers4

11

Python (since 2.6) has abstract base classes (aka virtual interfaces), which are more flexible than Java or C# interfaces. To check whether an object is iterable, use collections.Iterable:

if isinstance(obj, collections.Iterable):
    ...

However, if your else block would just raise an exception, then the most Python answer is: don't check! It's up to your caller to pass in an appropriate type; you just need to document that you're expecting an iterable object.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Yeah but should I do this kind of introspection check inside the same method or inside another `decorator` method? I would say in a decorator method to make it way more readable, but that would bring performance issues if heavily used in the table. What would be more **pythonic**? – NlightNFotis Dec 17 '12 at 18:26
  • 2
    @NlightNFotis the most pythonic thing to do is to document what you expect and allow `TypeError` to be raised if the caller passes in an inappropriate type. – ecatmur Dec 17 '12 at 18:35
11

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())
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • May I ask why is **invocation** prefered over **introspection**? Is it because introspection is costly? – NlightNFotis Dec 17 '12 at 18:36
  • 1
    @NlightNFotis: If you can just call the method, then you just saved yourself the test. Testing is an extra step, so if that can be avoided, just do so. – Martijn Pieters Dec 17 '12 at 18:37
  • @NlightNFotis: but also see [Python Forgiveness vs. Permission and Duck Typing](http://programmers.stackexchange.com/questions/175655/175663#175663) for performance tradeoffs between ducktyping and exception handling. – Martijn Pieters Dec 17 '12 at 18:38
  • I was gonna ask this but a phone call halted me! You're in my mind bro! Thanks for the answers. – NlightNFotis Dec 17 '12 at 18:42
2

The Pythonic way is to use duck typing and, "ask forgiveness, not permission". This usually means performing an operation in a try block assuming it behaves the way you expect it to, then handling other cases in an except block.

Silas Ray
  • 25,682
  • 5
  • 48
  • 63
  • This only makes sense if you already expect the instance to fit the interface and when you know that no side-effects will occur before you get an error. – Christopher Barber Aug 24 '16 at 15:58
1

I think this is the way that the community would recommend you do it:

import sys

def do_something(foo):
    try:
        for i in foo:
            process(i)
    except:
        t, ex, tb = sys.exc_info()
        if "is not iterable" in ex.message:
            print "Is not iterable"

do_something(True)

Or, you could use something like zope.interface.

bitcycle
  • 7,632
  • 16
  • 70
  • 121
  • 1
    I am quite sure, "the community" would not use an unspecified `except` statement, but would at least write `except TypeError:` to differentiate the error from other errors that might raise inside of `process()` – Jonathan Scholbach Mar 26 '20 at 18:31