Question

I'm using an object-oriented approach with inheritance to solve a problem, and I'm wondering how to apply 'Duck Typing' principles to this problem.

I have a class BoxOfShapes which would be instantiated with a list of Shapes ( Circle, Square and Rectangle)

import numpy as np

class Shape(object):
    def __init__(self,area):
        self.area = area;

    def dimStr(self):
        return 'area: %s' % str(self.area)

    def __repr__(self): 
        return '%s, %s' % (self.__class__.__name__, self.dimStr()) + ';'

class Circle(Shape):

    def __init__(self,radius): 
        self.radius = radius

    def dimStr(self):
        return 'radius %s' % str(self.radius)

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def dimStr(self):
        return '%s x %s' % (str(self.width), str(self.height))

class Square(Rectangle):

    def __init__(self, side):
        self.width = side
        self.height = side

class BoxOfShapes(object):

    def __init__(self, elements):
        self.elements = elements

    def __repr__(self):
        pass



listOfShapes = [Rectangle(10,13),Rectangle(9,5),Circle(12),Circle(8),Circle(36),Square(10)]

myBox = BoxOfShapes(listOfShapes)

print myBox

So lets look at the __repr__() method of BoxOfShapes. From what I understand, a duck-typing implementation would be something like,

def __repr__(self):
    return str(self.elements)

because this says 'I don't care what elements I have as long as they implement __str__() or __repr__(). The output of this is

>>> print myBox
[Rectangle, 10 x 13;, Rectangle, 9 x 5;, Circle, radius 12;, Circle, radius 8;, Circle, radius 36;, Square, 10 x 10;]

Lets say I want a more human-readable output from BoxOfShapes - I know all the shapes are of certain types, so it would be nice to categorize, them like so:

   def __repr__(self):
        circles = [ el.dimStr() for el in self.elements if isinstance(el, Circle)]
        squares = [ el.dimStr() for el in self.elements if isinstance(el, Square)]
        rectangles = [el.dimStr() for el in self.elements if (isinstance(el, Rectangle) and  not isinstance(el, Square)) ]

        return 'Box of Shapes; Circles: %s, Squares: %s, Rectangles: %s;' % ( str(circles), str(squares), str(rectangles))

The output of this is,

>>> print myBox
Box of Shapes; Circles: ['radius 12', 'radius 8', 'radius 36'], Squares: ['10 x 10'], Rectangles: ['10 x 13', '9 x 5'];

which is easier to read, but I am no longer using duck-typing and now I have to change my definition of BoxOfShapes any time I think up a new kind of shape.

My question is (how) would one apply duck-typing in this kind of scenario?

Was it helpful?

Solution

This isn't really about duck typing, but about inheritance generally (you could ask exactly the same question about Java, which has no concept of duck typing, for example).

What you want to do is simply to create a dictionary mapping types to list of instances. It's fairly easy to do that dynamically:

from collections import defaultdict
type_dict = defaultdict(list)
for element in self.elements:
    type_dict[element.type()].append(element.dimStr())
return ','.join('%s: %s' for k, v in type_dict.items())

OTHER TIPS

You've already paved the way to use inheritance effectively. You define a type method for each shape. Simply create a dictionary that maps the type to a list of elements of that type in your BoxOfShapes implementation.

As others have suggested, use the built-in type() function. If you want a string representation of the name of the shape, use a separate instance method.

This is one solution

from collections import defaultdict

class BoxOfShapes(object):
    def __init__(self, elements):
        self.elements = elements
        self.groupings = defaultdict(list)
        for element in elements:
            self.groupings[type(element)].append(element)

    def __repr__(self):
        return "Box of Shapes: %s;" % ", ".join(type(group[0]).__name__ + "s: " + str(group) for group in self.groupings.itervalues())

This doesn't seem ideal though.

A more suitable repr might just to be to return the len of self.elements.

def __repr__(self):
    return "<%s, length=%s>" % (type(self).__name__, len(self.elements))
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top