Is there a consensus on how to check a polymorphic instance?

Steven Bethard steven.bethard at gmail.com
Tue Nov 23 03:15:13 EST 2004


Mike Meng wrote:
> Here is my problem. My project needs to parse some user input
> string according to  specific rules. The rules can be defined with a
> string, a CSV file, an XML file, and other forms. Followed the standard
> OO style,  I designed a dictionary-like base class to abstract those
> different sources and provides other parts a uniform interface to query
> those rules.  I put some hard code into the base class and expect user
> to inherit from it.

The big question, I guess, is what do you want to happen if a user did 
not inherit from your base class, but still provides all the appropriate 
functionality?  In a dynamically-typed language like Python the answer 
is usually that the user's class should still be considered valid, 
though of course there are exceptions to this rule.

For example, say I write the following function:

 >>> def process_rules(rules):
...     for rule in rules:
...         print rule.name
...

Do I really care if the 'rules' object inherits from, say, the Rules 
class?  Probably not.  I care that it is iterable, and that the items 
that it contains have a name attribute.  Here's a few different ways I 
could write an object that conforms to this interface:

 >>> class Rule(object):
...     def __init__(self, name):
...         self.name = name
...
 >>> process_rules([Rule(s) for s in 'abc'])
a
b
c
 >>> class Rules(object):
...     def __init__(self, names):
...         self.names = names
...     def __iter__(self):
...         for name in self.names:
...             yield Rule(name)
...
 >>> process_rules(Rules('abc'))
a
b
c
 >>> class NastyRules(object):
...     def __init__(self, names):
...         self.names = names
...         self.index = -1
...     def __iter__(self):
...         return iter([self]*len(self.names))
...     def __getattr__(self, attr):
...         if attr == 'name':
...             self.index += 1
...             return self.names[self.index]
...         raise AttributeError
...
 >>> process_rules(NastyRules('abc'))
a
b
c

Of course, if you write your code like NastyRules, you should be drug 
out into the street and shot ;) but the point is that there are a 
variety of ways that a class could support the given interface and still 
do everything you've asked it to.  If you'd like to give your users the 
option to implement the interface in whatever way seems most 
appropriate, you shouldn't be testing isinstance:

 >>> isinstance([Rule(s) for s in 'abc'], list)
True
 >>> isinstance(Rules('abc'), list)
False
 >>> isinstance(NastyRules('abc'), list)
False

In the example above, if you test isinstance, you disallow your user 
from writing either of the other two implementations.

Note that you can still give useful error messages if you catch the 
appropriate exceptions:

 >>> def process_rules(rules):
...     try:
...         rules = iter(rules)
...     except TypeError:
...         raise TypeError('process_rules argument must '
...                         'support iterator protocol')
...     for rule in rules:
...         try:
...             print rule.name
...         except AttributeError:
...         	raise TypeError('process_rules argument must '
...                             'produce objects with name attribute')
...
 >>> process_rules([Rule(s) for s in 'abc'])
a
b
c
 >>> process_rules(Rules('abc'))
a
b
c
 >>> process_rules(NastyRules('abc'))
a
b
c
 >>> process_rules(1)
Traceback (most recent call last):
   File "<interactive input>", line 1, in ?
   File "<interactive input>", line 5, in process_rules
TypeError: process_rules argument must support iterator protocol
 >>> process_rules(range(10))
Traceback (most recent call last):
   File "<interactive input>", line 1, in ?
   File "<interactive input>", line 11, in process_rules
TypeError: process_rules argument must produce objects with name attribute

So again, the real question here is: If a user creates a class that 
appropriately implements the interface, but doesn't inherit from your 
base class, should their code fail?

Steve



More information about the Python-list mailing list