Hi Abe,
Thanks for your suggestions! We actually already considered the two
alternatives you propose.

*Multiple predicates per decorator. *The problem is that you can not deal
with toggling/describing individual contracts easily. While you can hack
your way through it (considering the arguments in the sequence, for
example), we found it clearer to have separate decorators. Moreover,
tracebacks are much easier to read, which is important when you debug a
program.

*AST magic. *The problem with any approach based on parsing (be it parsing
the code or the description) is that parsing is slow so you end up spending
a lot of cycles on contracts which might not be enabled (many contracts are
applied only in the testing environment, not int he production). Hence you
must have an approach that offers practically zero overhead cost to
importing a module when its contracts are turned off.

Decoding byte-code does not work as current decoding libraries can not keep
up with the changes in the language and the compiler hence they are always
lagging behind.

*Practicality of decorators. *We have retrospective meetings at the company
and I frequently survey the opinions related to the contracts (explicitly
asking about the readability and maintainability) -- so far nobody had any
difficulties and nobody was bothered by the noisy syntax. The decorator
syntax is simply not beautiful, no discussion about that. But when it comes
to maintenance,  there's a linter included (
https://github.com/Parquery/pyicontract-lint), and if you want contracts
rendered in an appealing way, there's a documentation tool for sphinx (
https://github.com/Parquery/sphinx-icontract). The linter facilitates the
maintainability a lot and sphinx tool gives you nice documentation for a
library so that you don't even have to look into the source code that often
if you don't want to.

We need to be careful not to mistake issues of aesthetics for practical
issues. Something might not be beautiful, but can be useful unless it's
unreadable.

*Conclusion. *What we do need at this moment, IMO, is a broad practical
experience of using contracts in Python. Once you make a change to the
language, it's impossible to undo. In contrast to what has been suggested
in the previous discussions (including my own voiced opinions), I actually
now don't think that introducing a language change would be beneficial *at
this precise moment*. We don't know what the use cases are, and there is no
practical experience to base the language change on.

I'd prefer to hear from people who actually use contracts in their
professional Python programming -- apart from the noisy syntax, how was the
experience? Did it help you catch bugs (and how many)? Were there big
problems with maintainability? Could you easily refactor? What were the
limits of the contracts you encountered? What kind of snapshot mechanism do
we need? How did you deal with multi-threading? And so on.

icontract library is already practically usable and, if you don't use
inheritance, dpcontracts is usable as well.  I would encourage everybody to
try out programming with contracts using an existing library and just hold
their nose when writing the noisy syntax. Once we unearthed deeper problems
related to contracts, I think it will be much easier and much more
convincing to write a proposal for introducing contracts in the core
language. If I had to write a proposal right now, it would be only based on
the experience of writing a humble 100K code base by a team of 5-10 people.
Not very convincing.


Cheers,
Marko

On Thu, 29 Nov 2018 at 02:26, Abe Dillon <abedil...@gmail.com> wrote:

> Marko, I have a few thoughts that might improve icontract.
> First, multiple clauses per decorator:
>
> @pre(
>     *lambda* x: x >= 0,
>     *lambda* y: y >= 0,
>     *lambda* width: width >= 0,
>     *lambda* height: height >= 0,
>     *lambda* x, width, img: x + width <= width_of(img),
>     *lambda* y, height, img: y + height <= height_of(img))
> @post(
>     *lambda* self: (self.x, self.y) in self,
>     *lambda* self: (self.x+self.width-1, self.y+self.height-1) in self,
>     *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, pt: Tuple[int, int]) -> bool:
>     x, y = pt
>     return (self.x <= x < self.x + self.width) and (self.y <= y < self.y +
> self.height)
>
>
> You might be able to get away with some magic by decorating a method just
> to flag it as using contracts:
>
>
> @contract  # <- does byte-code and/or AST voodoo
> *def* __init__(self, img: np.ndarray, x: int, y: int, width: int, height:
> int) -> None:
>     pre(x >= 0,
>         y >= 0,
>         width >= 0,
>         height >= 0,
>         x + width <= width_of(img),
>         y + height <= height_of(img))
>
>     # this would probably be declared at the class level
>     inv(*lambda* self: (self.x, self.y) in self,
>         *lambda* self: (self.x+self.width-1, self.y+self.height-1) in
> self,
>         *lambda* self: (self.x+self.width, self.y+self.height) not in
> self)
>
>     self.img = img[y : y+height, x : x+width].copy()
>     self.x = x
>     self.y = y
>     self.width = width
>     self.height = height
>
> That might be super tricky to implement, but it saves you some lambda
> noise. Also, I saw a forked thread in which you were considering some sort
> of transpiler  with similar syntax to the above example. That also works.
> Another thing to consider is that the role of descriptors
> <https://www.smallsurething.com/python-descriptors-made-simple/> overlaps
> some with the role of invariants. I don't know what to do with that
> knowledge, but it seems like it might be useful.
>
> Anyway, I hope those half-baked thoughts have *some* value...
>
> On Wed, Nov 28, 2018 at 1:12 AM Marko Ristin-Kaufmann <
> marko.ris...@gmail.com> wrote:
>
>> Hi Abe,
>>
>> I've been pulling a lot of ideas from the recent discussion on design by
>>> contract (DBC), the elegance and drawbacks
>>> <https://bemusement.org/doctests-arent-code> of doctests
>>> <https://docs.python.org/3/library/doctest.html>, and the amazing talk
>>> <https://www.youtube.com/watch?v=MYucYon2-lk> given by Hillel Wayne at
>>> this year's PyCon entitled "Beyond Unit Tests: Taking your Tests to the
>>> Next Level".
>>>
>>
>> Have you looked at the recent discussions regarding design-by-contract on
>> this list (
>> https://groups.google.com/forum/m/#!topic/python-ideas/JtMgpSyODTU
>> and the following forked threads)?
>>
>> You might want to have a look at static checking techniques such as
>> abstract interpretation. I hope to be able to work on such a tool for
>> Python in some two years from now. We can stay in touch if you are
>> interested.
>>
>> Re decorators: to my own surprise, using decorators in a larger code base
>> is completely practical including the  readability and maintenance of the
>> code. It's neither that ugly nor problematic as it might seem at first look.
>>
>> We use our https://github.com/Parquery/icontract at the company. Most of
>> the design choices come from practical issues we faced -- so you might want
>> to read the doc even if you don't plant to use the library.
>>
>> Some of the aspects we still haven't figured out are: how to approach
>> multi-threading (locking around the whole function with an additional
>> decorator?) and granularity of contract switches (right now we use
>> always/optimized, production/non-optimized and teating/slow, but it seems
>> that a larger system requires finer categories).
>>
>> Cheers Marko
>>
>>
>>
>>
_______________________________________________
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to