5

I found it seems useful to separate abstract method into two methods, one for public interface, the other to be overridden by subclasses.

This way you can add precondition / postcondition check for both input and output, making it robust against human errors.

But my concern here is whether it is pythonically acceptable or not, because in my little experience I've never seen code like this.

Normal polymorphism

import abc

class Shape:
    """Abstract base class for shapes"""
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def get_area(self, scale):
        """Calculates the area of the shape, scaled by a factor.
        Do not blame for a silly example.
        """
        pass

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

    def get_area(self, scale):
        return scale * self.width * self.height

print(Rectangle(10, 10, 40, 40).get_area(3))

# Gosh!... gets tons of 3's
print(Rectangle(10, 10, 40, 40).get_area((3,)))

Implementation method separated

import abc

class Shape:
    """Abstract base class for shapes"""
    __metaclass__ = abc.ABCMeta

    def get_area(self, scale):
        """Calculates the area of the shape, scaled by a factor"""

        # preconditions
        assert isinstance(scale, (int,float))
        assert scale > 0

        ret = self._get_area_impl(scale)

        # postconditions
        assert isinstance(ret, (int,float))
        assert ret > 0

        return ret

    @abc.abstractmethod
    def _get_area_impl(self, scale):
        """To be overridden"""
        pass

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

    def _get_area_impl(self, scale):
        return scale * self.width * self.height

print(Rectangle(10, 10, 40, 40).get_area(3))
print(Rectangle(10, 10, 40, 40).get_area((3,))) # Assertion fails
codegeek
  • 32,236
  • 12
  • 63
  • 63
niboshi
  • 1,448
  • 3
  • 12
  • 20
  • 1
    I'm not saying it is or it isn't, but it definitely is very strange to give the method to be overridden a "private" leading-underscore name. – Daniel Roseman Nov 12 '13 at 15:38
  • 3
    In Python, it is generally up to the *consumer* of an API to get the arguments correct. If the consumer wants to give you a `scale` that is a tuple, so be it, that's on their heads, not yours. – Martijn Pieters Nov 12 '13 at 15:53
  • i don't know if it's pythonic or not... it's not that the implementation is or isn't pythonic; as martijn said, it's just that python programmers don't tend towards this rigor. and in C etc. you don't need it, because you have static type checking. it will certainly have a significant perf impact. – Corley Brigman Nov 13 '13 at 15:25

1 Answers1

0

Here are the alternatives I see, in addition to the ones you described:

  1. Use the "implementation method separated" technique, but give it a friendlier name (no underscore, possibly a name that distinguishes it from the public API like get_area_from_validated_scale). I use this technique in my projects.
  2. Do the "implementation method separated" technique, where the user overrides the method but calls the base class method that does validation. (But gets more complicated if you need to validate input and output, though.)
  3. Create 2 methods on the base class, get_area_validate_input and get_area_validate_output, and require implementers to call those every time they implement get_area.
  4. Variation on #2 and #3, but done with a decorator, so the implementer only needs to put @area_validation above their method definition.
RexE
  • 17,085
  • 16
  • 58
  • 81