[Python-ideas] Pre-conditions and post-conditions

David Mertz mertz at gnosis.cx
Tue Aug 28 19:32:43 EDT 2018


I admit that I don't care that much about pre- and post-conditions. Like a
lot of people I've written a you library to handle some cases, but was
never motivates to make it better or use ones folks have refined. I
definitely oppose dedicated syntax.

Nonetheless, I'll say that it's really not hard to get a decorator to
cooperate with inheritance if you want that. If you decorate a parent class
you can simply attach the collection of invariants to the class as
notations, along with whatever actual guards the decorator enforces. An
inheriting child (also decorated) can just look up those notations and
apply them to the child.

I.e. the first thing a decorator can do is lookup the invariants attached
to the parent (if any) and apply them to the child (if this behavior is
enabled). Easy peasy.



On Tue, Aug 28, 2018, 12:35 PM Wes Turner <wes.turner at gmail.com> wrote:

> I now fully understand your use case.
>
> IIUC, what would make this easier is a syntax for defining preconditions,
> post conditions, and invariants that supports:
>
> * inheritance, composition (mixins)
>
> This isn't possible with decorators because there's no reference to the
> post-decorated function/method.
>
> A conventional method naming scheme and composition like
> _pre_check_[004]_height() would be unreasonably verbose; though it would
> then be possible to 'relax' specific contracts by name.
>
> * A better flow?
>
> Decorators subvert the normal top-down source layout (and so require
> up-down eye movements and working memory to remember what's going on)
>
> * Test case generation
>
> Generate tests and fixtures to verify the preconditions, postconditions,
> and invariants.
>
> * An Interpretor/compilation flag to skip the contracts/tests/assertions
>
> PYTHONOPTIMIZE (-O, -OO), __debug__
> Isn't there now an -X <...> interpretor flag in CPython?
>
> On Tuesday, August 28, 2018, Marko Ristin-Kaufmann <marko.ristin at gmail.com>
> wrote:
>
>> Hi,
>> To clarify the benefits of the contracts, let me give you an example from
>> our code base:
>>
>> @icontract.pre(lambda x: x >= 0)
>> @icontract.pre(lambda y: y >= 0)
>> @icontract.pre(lambda width: width >= 0)
>> @icontract.pre(lambda height: height >= 0)
>> @icontract.pre(lambda x, width, img: x + width <= pqry.opencv.width_of(img))
>> @icontract.pre(lambda y, height, img: y + height <= pqry.opencv.height_of(img))
>> @icontract.post(lambda self: (self.x, self.y) in self)
>> @icontract.post(lambda self: (self.x + self.width - 1, self.y + self.height - 1) in self)
>> @icontract.post(lambda self: (self.x + self.width, self.y + self.height) not in self)
>> def __init__(self, img: np.ndarray, x: int, y: int, width: int, height: int) -> None:
>>     self.img = img[y:y + height, x:x + width].copy()
>>     self.x = x
>>     self.y = y
>>     self.width = width
>>     self.height = height
>>
>> def __contains__(self, xy: Tuple[int, int]) -> bool:
>>     x, y = xy
>>     return self.x <= x < self.x + self.width and \
>>            self.y <= y < self.y + self.height
>>
>> We use mypy and type annotations for typing, and we don't need contracts
>> for that. In this particular case, contracts are very handy to formulate
>> how we deal with pixels. If you carefully look at the contracts, you will
>> see:
>> * pixels are indexed starting from 0 (as oppose to starting from 1)
>> * region-of-interest needs to fit within an image. It can not be outside
>> of its boundaries
>> * pixels within the region-of-interest are in [x, x + width), [y, y +
>> height) (where "[" means inclusive and ")" exclusive)
>>
>> *Why not types?* These constraints are either impossible or very hard to
>> write as types. Moreover, I doubt that you could give such types meaningful
>> names, and I expect the readability to suffer immensely. I suppose any
>> constraints involving more than a couple of variables is hard to write as a
>> type.
>>
>> *Benefits.* You could write all of this in human-readable informal
>> docstring of a class, but then it would never be verified. This way we are
>> sure that contracts are always there. Whenever we test, we can be sure that
>> all the contracted properties hold. Mind that we do not test that they hold
>> for that single test case -- we automatically test that they hold for all
>> the test cases.
>>
>> This is, in my opinion, far superior to ambiguous human documentation or
>> print statements (which are deleted after the debugging). I suppose *any*
>> larger code base built by a team of more than one developer needs this kind
>> of rigor. The contracts are not meant to be used only in high-risk
>> applications -- they are meant to improve any code base.
>>
>> *Problem without standard support. *The current libraries (dpcontracts,
>> icontract) can deal with pre and postconditions and class invariants of a
>> concrete class.
>>
>> However, the current libraries break as soon as you have inheritance.
>> There is no way in python to inherit the function decorators and to modify
>> them. If we are to inherit from the above-mentioned class ROI, we need to
>> make sure that the invariants of the parent class hold (*e.g., *what is
>> contained in a ROI) as well as invariants of the child class. We might want
>> to weaken the requirements (*e.g., *a ROI that can deal with pixels
>> outside of an image) such that x and y can be an arbitrary numbers, not
>> restricted to 0 <= x < img.width and 0 <= y < img.height respectively.
>>
>> Additionally, without standard approach, we do not know how to deal with
>> contracts when we have a third-party tool that would like to use them (*e.g.,
>> *for automatic generation of unit tests, static testing or visualization
>> in IDE or in documentation).
>>
>> @Wes Turner <wes.turner at gmail.com>: If I understood you correctly, you
>> are looking for a library that gives you verbose messages when a contract
>> is breached. Please have a look at icontract library:
>> https://github.com/Parquery/icontract
>>
>> We added informative messages particularly because we wanted to have
>> verbose output when something goes wrong.This was helpful not only during
>> the development, but also in production since failure cases were hard to
>> reproduce and anticipate in the first place (otherwise, they wouldn't have
>> made it into the production). Here is an example with an error message:
>>
>> >>> class B:...     def __init__(self) -> None:...         self.x = 7......     def y(self) -> int:...         return 2......     def __repr__(self) -> str:...         return "instance of B"...>>> class A:...     def __init__(self)->None:...         self.b = B()......     def __repr__(self) -> str:...         return "instance of A"...>>> SOME_GLOBAL_VAR = 13>>> @icontract.pre(lambda a: a.b.x + a.b.y() > SOME_GLOBAL_VAR)... def some_func(a: A) -> None:...     pass...>>> an_a = A()>>> some_func(an_a)
>> Traceback (most recent call last):
>>   ...
>> icontract.ViolationError: Precondition violated: (a.b.x + a.b.y()) > SOME_GLOBAL_VAR:SOME_GLOBAL_VAR was 13
>> a was instance of A
>> a.b was instance of B
>> a.b.x was 7
>> a.b.y() was 2
>>
>>
>> On Tue, 28 Aug 2018 at 03:19, Wes Turner <wes.turner at gmail.com> wrote:
>>
>>> Thanks for the explanation.
>>>
>>> This may be a bit OT,
>>> but is there a good way to do runtime assertions (in particular for data
>>> validation) that's as easy as assert?
>>>
>>> - self.assertEqual (unittest.TestCase.assertEqual) is hardly usable in
>>> other class instances,
>>> - pytest can be used at runtime but has somewhat nontrivial overhead
>>> - I always end up writing e.g. _assert() and _assertEqual() that throw
>>> AssertionErrors and helpful error messages just like unittest.
>>>
>>>
>>> How do contracts differ from checking interfaces with e.g.
>>> zope.interface?
>>> https://zopeinterface.readthedocs.io/en/latest/verify.html
>>>
>>> I'll read up a bit more on design by contract. I seem to have
>>> conceptualized dbc as a composition approach with typed interfaces and type
>>> and value assertions, but that's not it?
>>>
>>> If the parameter value assertions in pycontracts aren't really
>>> contracts, and the mypy compile-time parameter and return type checks are
>>> not really contracts, and zope.interface verifications aren't really
>>> contracts; what does that leave in terms of abstract data types,
>>> preconditions, postconditions, and invariants?
>>>
>>> https://en.wikipedia.org/wiki/Design_by_contract
>>>
>>> On Monday, August 27, 2018, Steven D'Aprano <steve at pearwood.info >
>>> wrote:
>>>
>>>> On Mon, Aug 27, 2018 at 11:01:21AM -0400, Wes Turner wrote:
>>>> > Runtime checks: data validation & code validation
>>>> > Compile-time checks: code validation
>>>> >
>>>> > What sort of data validation is appropriate for assert statements or
>>>> > contacts that may be skipped due to trading performance for more risk
>>>> > ('optimized out')?
>>>>
>>>> That depends on what you mean by "data validation".
>>>>
>>>> Testing for bad input, or testing for predictable error states such as
>>>> I/O errors, missing files, permission errors, server down etc is
>>>> not appropriate for assertions (which includes contracts).
>>>>
>>>> The rule I use is that assertions are for:
>>>>
>>>> (1) testing your program state, which is under your control; and
>>>>
>>>> (2) communicating the intention of your program as executable
>>>>     code rather than comments.
>>>>
>>>> The ultimate aim of contracts and assertions is to eventually disable
>>>> them when the program is bug-free.
>>>>
>>>> The Eiffel docs say:
>>>>
>>>>     It should be clear from the preceding discussion that contracts
>>>>     are not a mechanism to test for special conditions, for example
>>>>     erroneous user input. For that purpose, the usual control
>>>>     structures [...] are available [...] An assertion is instead a
>>>>     correctness condition governing the relationship between two
>>>>     software modules (not a software module and a human, or a
>>>>     software module and an external device). ... Bluntly:
>>>>
>>>>     Rule -- Assertion Violation: A run-time assertion violation is
>>>>     the manifestation of a bug.
>>>>
>>>>
>>>> https://www.eiffel.org/doc/eiffel/ET-_Design_by_Contract_%28tm%29%2C_Assertions_and_Exceptions
>>>>
>>>> For detecting *external* error states (anything to do with data that
>>>> comes from outside your program, like user input) you can never let
>>>> your
>>>> guard down and never disable the test, because servers can always go
>>>> down, users can always give you bad data, files can always be corrupt.
>>>> It is unsafe to disable these tests and so these should not be
>>>> assertions.
>>>>
>>>> For a library function intended to be called by third-parties, the
>>>> function arguments aren't under the control of the library author so
>>>> should not be tested with assertions. But for an application where the
>>>> author controls those function arguments, they are under the author's
>>>> control and may be assertions or contracts.
>>>>
>>>> Design By Contract is partly a methodology and partly a set of syntax.
>>>> Its a way of thinking about the design of your program. In practice,
>>>> you
>>>> don't have to buy 100% into DBC to get some benefit for it. A study
>>>> done
>>>> a few years back looked at 21 large projects in Eiffel, JML (Java) and
>>>> Code Contracts for C# and found that 33% of the classes used contracts.
>>>>
>>>> http://se.ethz.ch/~meyer/publications/methodology/contract_analysis.pdf
>>>>
>>>> Like unit testing, you don't need 100% coverage to get benefit. 10% is
>>>> better than nothing, and 20% is better than 10%.
>>>>
>>>> I wrote more about assertions here:
>>>>
>>>> https://import-that.dreamwidth.org/676.html
>>>>
>>>>
>>>> --
>>>> Steve
>>>> _______________________________________________
>>>> Python-ideas mailing list
>>>> Python-ideas at python.org
>>>> https://mail.python.org/mailman/listinfo/python-ideas
>>>> Code of Conduct: http://python.org/psf/codeofconduct/
>>>>
>>> _______________________________________________
>>> Python-ideas mailing list
>>> Python-ideas at python.org
>>> https://mail.python.org/mailman/listinfo/python-ideas
>>> Code of Conduct: http://python.org/psf/codeofconduct/
>>>
>> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180828/0d47a256/attachment-0001.html>


More information about the Python-ideas mailing list