_______________________________________________
Python-ideas mailing list
Python...@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/
|
|
class Account:
def __init__(self, balance: int) -> None:
self.balance = balance
self.deposits = [] # type: List[int]
def update(self, sum: int)->None:
require: sum >= 0
self.balance += sum
self.deposits.append(sum)
ensure: len(self.deposits) = len(old self.deposits) + 1, "one more deposit"
ensure: self.balance = old self.balance + sum, "updated"
invariant: not self.deposits or self.balance == sum(self.deposits), "consistent balance"
invariant: self.deposits or self.balance == 0, "zero if no deposits"
class CheckingAccount(Account):
def update(self, sum: int) -> None:
require else: self.balance >= sum
self.balance += sum
self.deposits.append(sum)
invariant: self.balance >= 0
@contract
def my_function(a : 'int,>0', b : 'list[N],N>0') -> 'list[N]':
# Requires b to be a nonempty list, and the return
# value to have the same length.
...
new_contract('valid_name', lambda s: isinstance(s, str) and len(s)>0)
@contract(names='dict(int: (valid_name, int))')
def process_accounting(records):
...
>>> @require("`i` must be a positive integer",
... lambda args: isinstance(args.i, int) and args.i > 0)
... @require("`j` must be a positive integer",
... lambda args: isinstance(args.j, int) and args.j > 0)
... @ensure("the result must be greater than either `i` or `j`",
... lambda args, result: result > args.i and result > args.j)
... def add2(i, j):
... if i == 7:
... i = -7 # intentionally broken for purposes of example
... return i + j>>> @invariant("inner list can never be empty", lambda self: len(self.lst) > 0)
... @invariant("inner list must consist only of integers",
... lambda self: all(isinstance(x, int) for x in self.lst))
... class NonemptyList:
... @require("initial list must be a list", lambda args: isinstance(args.initial, list))
... @require("initial list cannot be empty", lambda args: len(args.initial) > 0)
... @ensure("the list instance variable is equal to the given argument",
... lambda args, result: args.self.lst == args.initial)
... @ensure("the list instance variable is not an alias to the given argument",
... lambda args, result: args.self.lst is not args.initial)
... def __init__(self, initial):
... self.lst = initial[:]
...
... def get(self, i):
... return self.lst[i]
...
... def pop(self):
... self.lst.pop()
...
... def as_string(self):
... # Build up a string representation using the `get` method,
... # to illustrate methods calling methods with invariants.
... return ",".join(str(self.get(i)) for i in range(0, len(self.lst)))>>> @icontract.pre(lambda x: x > 3) ... def some_func(x: int, y: int = 5)->None: ... pass ... >>> some_func(x=5) # Pre-condition violation >>> some_func(x=1) Traceback (most recent call last): ... icontract.ViolationError: Precondition violated: x > 3: x was 1Mind that there is no mandatory description in the contract yet the message is informative. We achieve that by re-executing the condition function and tracing the values by examining its abstract syntax tree. The re-computation can also deal with more complex expressions and outer scope:
>>> @icontract.post(lambda result, x: result > x) ... def some_func(x: int, y: int = 5) -> int: ... return x - y ... >>> some_func(x=10) Traceback (most recent call last): ... icontract.ViolationError: Post-condition violated: result > x: result was 5 x was 10
>>> 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 Mon, Aug 27, 2018 at 09:24:20AM +0100, Ivan Levkivskyi wrote:
> TBH, I think one of the main points of design by contract is that contracts
> are verified statically.
No, that's not correct. Contracts may be verified statically if the
compiler is able to do so, but they are considered runtime checks.
@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
Wes Turner wrote:
> I'm going to re-write that in a pseudo-Eiffel like syntax:
Maybe some magic could be done to make this work:
def __init__(self, img: np.ndarray, x: int, y: int, width: int,
height: int) -> None:
def __require__():
x >= 0
y >= 0
width >= 0
height >= 0
x + width <= pqry.opencv.width_of(img)
y + height <= pqry.opencv.height_of(img)
def __ensure__():
(self.x, self.y) in self
(self.x + self.width - 1, self.y + self.height - 1) in self
(self.x + self.width, self.y + self.height) not in self
# body of __init__ goes here...
The last example is about pre-post conditions in a class constructor.
But I imagine this is not the only case where one would want to define
contracts, like probably: within methods that return something and to
functions (and in both cases, we'd want to contractually state some of
the return's specificities).
Is the following function something someone used to work with contracts
would write?
def calculate(first: int, second: int) -> float:
def __require__():
first > second
second > 0
# or first > second > 0 ?
def __ensure__(ret): # we automatically pass the return of the
function to this one
ret > 1
return first / second
If so, having a reference to the function's output would probably be
needed, as in the example above.
Also, wouldn't someone who use contracts want the type hints he provided
to be ensured without having to add requirements like `type(first) is
int` or something?
- Brice
Le 29/08/2018 à 07:52, Greg Ewing a écrit :
> Wes Turner wrote:
>> I'm going to re-write that in a pseudo-Eiffel like syntax:
>
> Maybe some magic could be done to make this work:
>
> def __init__(self, img: np.ndarray, x: int, y: int, width: int,
> height: int) -> None:
>
> def __require__():
> x >= 0
> y >= 0
> width >= 0
> height >= 0
> x + width <= pqry.opencv.width_of(img)
> y + height <= pqry.opencv.height_of(img)
>
> def __ensure__():
> (self.x, self.y) in self
> (self.x + self.width - 1, self.y + self.height - 1) in self
> (self.x + self.width, self.y + self.height) not in self
>
> # body of __init__ goes here...
>
_______________________________________________
On Tue, Aug 28, 2018 at 07:46:02AM +0200, Marko Ristin-Kaufmann 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:
Thanks for your example Marko. I think that is quite close to the
ugliest piece of Python code I've ever seen, and I don't mean that as a
criticism of you for writing it or the icontract library's design.
We are back then to the core question. These techniques are simple, demonstrably useful, practical, validated by years of use, explained in professional books (e.g. [6]), introductory programming textbooks (e.g. [7]), EdX MOOCs (e.g. [8]), YouTube videos, online tutorials at eiffel.org, and hundreds of articles cited thousands of times.
Is there any logical or empirical objection that the design-by-contract is not useful and hence does not merit to be introduced into the core language? There is little point in writing a PEP and fleshing out the details if the community will reject it on grounds that design-by-contract by itself is meaningless. So I'd suggest we clarify this first before we move on.6. Bertrand Meyer, Object-Oriented Software Construction, 2nd edition, Prentice Hall, 1997.
7. Bertrand Meyer, Touch of Class: Learning to Program Well Using Objects and Contracts, Springer, 2009, see touch.ethz.ch and Amazon page.
8. MOOCs (online courses) on EdX : "Computer: Art, Magic, Science", Part 1 and Part 2. (Go to "archived versions" to follow the courses.)
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.
class A:
@icontract.pre(lambda x: x > 0)
def some_method(self, x: int)->None:
pass
class B(A):
# Precondition should be inherited here.
def some_method(self, x: int) -> None:
pass
Could you please elaborate a bit? I don't see how the annotations would make the contracts invoked on inheritance. Consider this simple case:class A:
@icontract.pre(lambda x: x > 0)
def some_method(self, x: int)->None:
pass
class B(A):
# Precondition should be inherited here.
def some_method(self, x: int) -> None:
passYou would still need to somehow decorate manually the overridden methods even though you would not specify any new contracts, right? Is there a mechanism in Python that I am not aware of that would allow us to accomplish that?
class B(A):@inherit_invariants
def some_method(self, x: int) -> None:pass
@precondition(lambda x: x=42, inherit_parent=True)
def other_method(self, x: int) -> float:
return 42/5
Is there any logical or empirical objection that the design-by-contract is not useful and hence does not merit to be introduced into the core language? There is little point in writing a PEP and fleshing out the details if the community will reject it on grounds that design-by-contract by itself is meaningless.
_______________________________________________
Python-ideas mailing list
Python...@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/
On Wed, 29 Aug 2018 at 13:18, Steven D'Aprano <st...@pearwood.info> wrote:I didn't want to embarass Ivan any further by seemingly picking on his
opinion about contracts being always statically checked, but when I
asked off-list I got told to reword and post it here. So here it is.
Sorry Ivan if this makes you feel I'm picking on you, that isn't my
intention.
NP, the discussion just shift more towards terminology etc. which is less interesting TBH.--Ivan
I think a metaclass is a non-starter. If one were used, it would
preclude using contracts in any case where a metaclass were already
used, or where one was needed in the future. I'm sure people will
disagree with me on this.
But, I think a more productive line of thinking is: what could be added
to the language that would let contracts be implementable, and could
also be used for other things, too? Sort of like how PEP 487 adds
customizability that has wide applicability.
Eric
Adding a new feature, even if it is *technically* backwards compatible, has HUGE costs.
It's a specific feature that is relatively well defined as a concept (and precisely defined in regard to Eiffel specifically; but we might not implement those *exact* semantics). It's also a feature that no languages in particularly widespread use have decided to implement at the language level. I've chatted with Meyer; he's definitely very smart and definitely strongly opinionated, but I also think he's wrong about the overall importance of this feature versus lots of others [emphasis Marko].
In my mind, this feature doesn't come close to meeting the burden of those high costs listed above (and others I did not mention). But I don't have any say in what the core developers will do, beyond in that they might be influenced by my opinion here.
Related to the text I emphasized, would you mind to explain a bit more in-depth which features you have in mind? I see contracts formally written out and automatically verified as a completely indispensable tool in large projects with multiple people involved.
1. How exactly do these differ from simple assertions? I don't
understand the hints about "relaxing" and "strengthening" contracts in
subclasses, in particular I've no idea how I'd express that in actual
syntax.
class A:
# A.some_func operates on sorted lists of integers and
# it returns an integer larger than the length of
# the input list.
@icontract.pre(lambda lst: lst == sorted(lst))
@icontract.post(lambda result: result > len(lst))
def some_func(self, lst: List[int]) -> int:
# ...
class B(A):
# B.some_func inherits contracts from A, but overrides the implementation ->
# B.some_func also operates only on sorted lists of integers
# and needs to return an integer larger than the length
# of the input list.
def some_func(self, lst: List[int]) -> int:
# ...
class C(A):
# C.some_func also inherits the contracts from A.
# It weakens the precondition:
# it operates either on sorted lists OR
# the lists that are shorter than 10 elements.
#
# It strenghthens the postcondition:
# It needs to return an integer larger than
# the length of the input list AND
# the result needs to be divisible by 2.
@icontract.post(lambda result: result % 2 == 0)
def some_func(self, lst: List[int]) -> int:
# ...
2. Are we talking here about adding checks that would run in
production code? Wouldn't that slow code down? How do I deal with the
conflict between wanting tighter checks but not wanting to pay the
cost at runtime of checking things that should never go wrong
(contracts, as I understand it, are for protecting against code bugs,
that's why there's no facility for trapping them and producing "user
friendly" error messages)? I use assertions very sparingly in
production code for precisely that reason - why would I be more
willing to use contracts?
Related to the text I emphasized, would you mind to explain a bit more in-depth which features you have in mind? I see contracts formally written out and automatically verified as a completely indispensable tool in large projects with multiple people involved.This much is rather self-evidently untrue. There are hundreds or thousands of large projects involving multiple people, written in Python, that have succeeded without using contracts. That suggests the feature is not "completely indispensable" since it was dispensed with in the vast majority of large projects.
Jonathan Fine wrote:
> I've just read and article which makes a good case for providing
> pre-conditions and post-conditions.
>
> http://pgbovine.net/python-unreadable.htm
There's nothing in there that talks about PBC-style executable
preconditions and postconditions, it's all about documenting
the large-scale intent and purpose of code. He doesn't put
forward any argument why executable code should be a better
way to do that than writing comments.
assert test, "error message is the test fail"
I mean, you just write your test, dev get a feedback on problems, and
prod can remove all assert using -o.
What more do you need ?
Le 15/08/2018 à 23:06, Marko Ristin-Kaufmann a écrit :
> Hi,
>
> I would be very interested to bring design-by-contract into python 3. I
> find design-by-contract particularly interesting and indispensable for
> larger projects and automatic generation of unit tests.
>
> I looked at some of the packages found on pypi and also we rolled our
> own solution (https://github.com/Parquery/icontract/
> <https://github.com/Parquery/icontract/>). I also looked into
> https://www.python.org/dev/peps/pep-0316/
> <https://www.python.org/dev/peps/pep-0316/>.
>
> However, all the current solutions seem quite clunky to me. The
> decorators involve an unnecessary computational overhead and the
> implementation of icontract became quite tricky once we wanted to get
> the default values of the decorated function.
>
> Could somebody update me on the state of the discussion on this matter?
>
> I'm very grateful for any feedback on this!
@icontract.inv(lambda self: self.name.strip() == self.name)The postcondition tells us that the resulting map keys the values on their name property.
@icontract.inv(lambda self: self.line.endswith("\n"))
class Requirement:
"""Represent a requirement in requirements.txt."""
def __init__(self, name: str, line: str) -> None:
"""
Initialize.
:param name: package name
:param line: line in the requirements.txt file
"""
...
@icontract.post(lambda result: all(val.name == key for key, val in result.items()))
def parse_requirements(text: str, filename: str = '<unknown>') -> Mapping[str, Requirement]:
"""
Parse requirements file and return package name -> package requirement as in requirements.txt
:param text: content of the ``requirements.txt``
:param filename: where we got the ``requirements.txt`` from (URL or path)
:return: name of the requirement (*i.e.* pip package) -> parsed requirement
"""
...
@icontract.post(lambda result: len(result) == len(set(result)), enabled=icontract.SLOW)Here is a bit more complex example.
def missing_requirements(module_to_requirement: Mapping[str, str],
requirements: Mapping[str, Requirement]) -> List[str]:
"""
List requirements from module_to_requirement missing in the ``requirements``.
:param module_to_requirement: parsed ``module_to_requiremnt.tsv``
:param requirements: parsed ``requirements.txt``
:return: list of requirement names
"""
...
@icontract.pre(lambda rel_paths: all(rel_pth.root == "" for rel_pth in rel_paths)) # A
@icontract.post(
lambda rel_paths, result: all(pth in result.rel_paths for pth in rel_paths),
enabled=icontract.SLOW,
description="Initial relative paths included") # B
@icontract.post(
lambda requirements, result: all(req.name in requirements for req in result.requirements),
enabled=icontract.SLOW) # C
@icontract.pre(
lambda requirements, module_to_requirement: missing_requirements(module_to_requirement, requirements) == [],
enabled=icontract.SLOW) # D
def collect_dependency_graph(root_dir: pathlib.Path, rel_paths: List[pathlib.Path],
requirements: Mapping[str, Requirement],
module_to_requirement: Mapping[str, str]) -> Package:
"""I hope these examples convince you (at least a little bit :-)) that contracts are easier and clearer to write than asserts. As noted before in this thread, you can have the same behavior with asserts as long as you don't need to inherit the contracts. But the contract decorators make it very explicit what conditions should hold without having to look into the implementation. Moreover, it is very hard to ensure the postconditions with asserts as soon as you have a complex control flow since you would need to duplicate the assert at every return statement. (You could implement a context manager that ensures the postconditions, but a context manager is not more readable than decorators and you have to duplicate them as documentation in the docstring).
Collect the dependency graph of the initial set of python files from the code base.
:param root_dir: root directory of the codebase such as "/home/marko/workspace/pqry/production/src/py"
:param rel_paths: initial set of python files that we want to package. These paths are relative to root_dir.
:param requirements: requirements of the whole code base, mapped by package name
:param module_to_requirement: module to requirement correspondence of the whole code base
:return: resolved depedendency graph including the given initial relative paths,
"""
_______________________________________________
Python-ideas mailing list
Python...@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/
The contracts make merely tests obsolete that test that the function or class actually observes the contracts.
Please let me know what points do not convince you that Python needs contracts
I'm afraid that in reading the examples provided it is difficulties for me not simply to think that EVERY SINGLE ONE of them would be FAR easier to read if it were an `assert` instead.
The API of the library is a bit noisy, but I think the obstacle it's more in the higher level design for me. Adding many layers of expensive runtime checks and many lines of code in order to assure simple predicates that a glance at the code or unit tests would do better seems wasteful.
The API of the library is a bit noisy, but I think the obstacle it's more in the higher level design for me. Adding many layers of expensive runtime checks and many lines of code in order to assure simple predicates that a glance at the code or unit tests would do better seems wasteful.
I just want to point out that you don't need permission from anybody to start a library. I think developing and popularizing a contracts library is a reasonable goal -- but that's something you can start doing at any time without waiting for consensus.
Your contracts are only checked when the function is evaluated, so you'd still need to write that unit test that confirms the function actually observes the contract. I don't think you necessarily get to reduce the number of tests you'd need to write.
There's also no guarantee that your contracts will necessarily be *accurate*. It's entirely possible that your preconditions/postconditions might hold for every test case you can think of, but end up failing when running in production due to some edge case that you missed.
(And if you decide to disable those pre/post conditions to avoid the efficiency hit, you're back to square zero.)
Or I guess to put it another way -- it seems what all of these contract libraries are doing is basically adding syntax to try and make adding asserts in various places more ergonomic, and not much else. I agree those kinds of libraries can be useful, but I don't think they're necessarily useful enough to be part of the standard library or to be a technique Python programmers should automatically use by default.
What might be interesting is somebody wrote a library that does something more then just adding asserts. For example, one idea might be to try hooking up a contracts library to hypothesis (or any other library that does quickcheck-style testing). That might be a good way of partially addressing the problems up above -- you write out your invariants, and a testing library extracts that information and uses it to automatically synthesize interesting test cases.
class Component(abc.ABC, icontract.DBC):
"""Initialize a single component."""
@staticmethod
@abc.abstractmethod
def user() -> str:
"""
Get the user name.
:return: user which executes this component.
"""
pass
@staticmethod
@abc.abstractmethod
@icontract.post(lambda result: result in groups())
def primary_group() -> str:
"""
Get the primary group.
:return: primary group of this component
"""
pass
@staticmethod
@abc.abstractmethod
@icontract.post(lambda result: result.issubset(groups()))
def secondary_groups() -> Set[str]:
"""
Get the secondary groups.
:return: list of secondary groups
"""
pass
@staticmethod
@abc.abstractmethod
@icontract.post(lambda result: all(not pth.is_absolute() for pth in result))
def bin_paths(config: mapried.config.Config) -> List[pathlib.Path]:
"""
Get list of binary paths used by this component.
:param config: of the instance
:return: list of paths to binaries used by this component
"""
pass
@staticmethod
@abc.abstractmethod
@icontract.post(lambda result: all(not pth.is_absolute() for pth in result))
def py_paths(config: mapried.config.Config) -> List[pathlib.Path]:
"""
Get list of py paths used by this component.
:param config: of the instance
:return: list of paths to python executables used by this component
"""
pass
@staticmethod
@abc.abstractmethod
@icontract.post(lambda result: all(not pth.is_absolute() for pth in result))
def dirs(config: mapried.config.Config) -> List[pathlib.Path]:
"""
Get directories used by this component.
:param config: of the instance
:return: list of paths to directories used by this component
"""
pass