(Posting from work, so sorry for the short response.)
@Paul Moore firstname.lastname@example.org icontract.pre/post/inv have the enabled argument; if not enabled, the contract is ignored.
Similarly with rmdir() -- "the directory must be empty" -- but how exactly
am I supposed to check that?
Isn't that the whole point? The prose statement "the directory must be empty" is clear. But the exact code check isn't - and may be best handled by a series of unit tests, rather than a precondition.
I meant "check" as a user, not as a developer. As in "What did the implementer think -- how am I supposed to check that the directory is empty?" A la: "Dear user, if you want to rmdir, here is what you need to check that it is indeed a dir, and here is what you need to check that it is empty. If both checks pass, run me."
@David patching __doc__ automatically is on the short-term todo list. I suppose we'll just add sphinx directives (:requires:, :ensures: etc.)
* Marko isn't that familiar with the codebase, so there may be better
ways to express certain things
This is true :)
* Sometimes it's just plain hard to express a verbal constraint in code
In these cases you simply don't express it in code. Why would you? If it's complex code, possibility that you have an error is probably equal or higher than that your comment rots.
@pre(lambda args, result: not any(Path(arg).is_absolute() for arg in args)
or (result == [pth for arg in args for pth in [Path(arg)] if pth.is_absolute()][-1]), "When several absolute paths are given, the last is taken as an anchor (mimicking os.path.join()’s behaviour)")
I'm really not familiar with the code base nor with how to write stuff nice and succinct in python. This particular contract was hard to define because there were no last() and no arg_is_absolute() functions.
Otherwise, it would have read:
@pre(lambda args, result: not any(arg_is_absolute(arg) for arg in args) or result == Path(last(arg for arg in args if arg_is_absolute(arg)))
When rendered, this wouldn't look too bad to read.
It is still fundamentally difficult to make assertions about the file system as pre/post contracts. Are you becoming aware of this? Contracts, as has been stated multiple times, look great for mathematically pure functions that have no state outside of their own parameters and return values (and 'self', where applicable), but are just poor versions of unit tests when there's anything external to consider.
I never thought of these particular contracts as running in the production. I would set them to run only in testing and only on part of the tests where they are safe from race conditions (e.g., setting enabled=test_pathlib.SERIAL; toggling mechanism is something I haven't spent too much thought about either and would also need to be discussed/implemented.).
I really thought about them as documentation, not for correctness (or at best deeper tests during the testing in a "safe" local environment, for example when you want to check if all the contracts also hold on situations in *my *testsuite, not only during the test suite of pathlib).
In the end, I'm calling it the day. I really got tired in the last few days. Standardizing contracts for python is not worth the pain. We'll continue to develop icontract for our internal needs and keep it open source, so anybody who's interested can have a look. Thank you all for a very lively discussions!
On Fri, 28 Sep 2018 at 14:49, Paul Moore email@example.com wrote:
On Fri, 28 Sep 2018 at 13:23, David Mertz firstname.lastname@example.org wrote:
I agree that all the Sphinx documentation examples shown are very nice.
Prose descriptions would often be nicer still, but the Boolean expressions are helpful for those unusual cases where I don't want to look at the code.
I'm ambivalent about the Sphinx examples. I find the highly detailed code needed to express a condition fairly unreadable (and I'm an experienced Python programmer). For example
@pre(lambda args, result: not any(Path(arg).is_absolute() for arg in args) or (result == [pth for arg in args for pth in [Path(arg)] if pth.is_absolute()][-1]), "When several absolute paths are given, the last is taken as an anchor (mimicking os.path.join()’s behaviour)")
The only way I'd read that is by looking at the summary text - I'd never get the sense of what was going on from the code alone. There's clearly a number of trade-offs going on here:
- Conditions should be short, to avoid clutter
- Writing helper functions that are *only* used in conditions is more
code to test or get wrong
- Sometimes it's just plain hard to express a verbal constraint in code
- Marko isn't that familiar with the codebase, so there may be better
ways to express certain things
But given that *all* the examples I've seen of contracts have this issue (difficult to read expressions) I suspect the problem is inherent.
Another thing that I haven't yet seen clearly explained. How do these contracts get *run*? Are they checked on every call to the function, even in production code? Is there a means to turn them off? What's the runtime overhead of a "turned off" contract (even if it's just an if-test per condition, that can still add up)? And what happens if a contract fails - is it an exception/traceback (which is often unacceptable in production code such as services)? The lack of any clear feel for the cost of adding contracts is part of what makes people reluctant to use them (particularly in the face of the unfortunately still common assertion that "Python is slow" :-()