Use polymorphism and duck-typing before
You generally define what you want to do with the shapes, then either use polymorphism to adjust how each shape responds to what you want to do, or you use duck typing; test if the shape 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
So you need to work out why you need to filter on class in the first place; why do you need to find all the rectangles? Perhaps they are the only type that implements a certain method, in which case you use duck typing to test for that method instead.
Or perhaps you need to process each type in a different way, because each type has to be handled just right for whatever you are doing with them. In that case, you need to give each shape the same method, but implement it differently for each type, placing the responsibility for adaptation in each object.
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:
# it's a rectangle
but it also let's you look up adapters; instead of putting all the behaviours for every use of shapes in your shape classes, you implement adapters to provide polymorphic behaviours for specific use-cases. You can adapt your shapes to be printable, or paintable, or exportable to XML:
def __init__(self, rectangle):
self.rectangle = rectangle
and your code merely has to look up adapters for each shape:
for shape in shapebag: