1

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?

AnjoMan
  • 4,838
  • 4
  • 20
  • 28
  • `__repr__` is supposed to be a lot less ambiguous that what you're using. In particular, it should be using the elements' `__repr__`, and it should probably show the order of the elements. A good thing to try for is to make `eval(repr(thing)) == thing`, or as close as you can get as possible. I recommend `return 'BoxOfShapes({})'.format(repr(self.elements))` – user2357112 Aug 16 '13 at 21:34
  • If you want something pretty and human-readable, that's what `__str__` is for. – user2357112 Aug 16 '13 at 21:36
  • 2
    What's the deal with those `type()` static methods? You should probably lose them. If you need the name of an object's type, there's the built-in `type()` function and the `__name__` attribute of types (and more often than not, you can just use the type object rather than fooling around with its name). –  Aug 16 '13 at 21:40
  • And you can't access the `staticmethod`s of an instance very easily, you'd be better off having the type as a plain attribute on the class. – GP89 Aug 16 '13 at 21:51
  • @delnan @GP89 you are right, I should have been using `self.__class__.__name__` to get the class name of each shape, which was what I was using my `type()` method for. I've edited it to reflect your advice. – AnjoMan Aug 16 '13 at 22:01
  • @user2357112 I'm not sure if I agree with you entirely `__repr__()`, because if I have a list of objects that are of type `Shape` and I call str() on that list, python goes through the list and calls `__repr__()` on each element, not `__str__()`, so now even though I print the pretty human-readable `str()` of my list, the elements themselves are not represented by something pretty and human-readable. Maybe I'm being short-sighted, but `print mList` is more concise than `print [ str(el) for el in mList]` – AnjoMan Aug 16 '13 at 22:12
  • 1
    @AnjoMan: Container objects' `__str__` uses the elements' `__repr__` because `['3', 3, 'a, b', '']` is more human readable than `[3, 3, a, b, ]`. – user2357112 Aug 16 '13 at 22:20
  • Useful: http://stackoverflow.com/questions/1436703/difference-between-str-and-repr-in-python – user2357112 Aug 16 '13 at 22:23

3 Answers3

1

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.

Colonel Panic
  • 1,604
  • 2
  • 20
  • 31
1

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))
GP89
  • 6,600
  • 4
  • 36
  • 64
1

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())
Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895