In my experience, design-by-contract is worth doing, even without language support. For methods that aren't designed to be overridden, assertions and docstrings are sufficient for both pre- and post-conditions. For methods that are designed to be overridden, we split the method in two: a public method which checks the pre- and post-conditions, and a protected method which provides the implementation, and may be overridden by subclasses. Here an example of the latter:
class Math:
def square_root(self, number)
"""
Calculate the square-root of C{number}
@precondition: C{number >= 0}
@postcondition: C{abs(result * result - number) < 0.01}
"""
assert number >= 0
result = self._square_root(number)
assert abs(result * result - number) < 0.01
return result
def _square_root(self, number):
"""
Abstract method for implementing L{square_root()}
"""
raise NotImplementedError()
I got the square root as a general example of design-by-contract from an episode on design-by-contract on software-engineering radio. They also mentioned the need for language support, claiming that assertions don't help enforce the Liskov substitution principle, though my example above aims to demonstrate otherwise. I should also mention the C++ pimpl (private implementation) idiom as a source of inspiration, though that has an entirely different purpose.
In my work, I recently refactored this kind of contract-checking into a larger class hierarchy (the contract was already documented, but not systematically tested). Existing unit-tests revealed that the contracts were violated multiple times. I can only conclude this should have been done a long time ago, and that unit-test coverage pays off even more once design-by-contract is applied. I expect anyone who tries out this combination of techniques to make the same observations.
Better tool-support may offer us even more power in the future; I would welcome that.