[Python-Dev] Contracts PEP (was re: Introduction)
Phillip J. Eby
pje@telecommunity.com
Wed, 28 May 2003 18:05:30 -0400
At 03:37 PM 5/28/03 -0400, Terence Way wrote:
>On Wednesday, May 28, 2003, at 02:23 PM, Phillip J. Eby wrote:
>>Also, I didn't find the motivation section convincing. Your answer to
>>"Why not have several different implementations, or let programmers
>>implement their own assertions?" isn't actually a justification. If
>>Alice uses some package to wrap her methods with checks, I can weaken the
>>preconditions in a subclass, by simply overriding the methods. If I
>>can't do that, then it is a weakness of the DBC package Alice used, or of
>>Alice's package, not a weakness of Python.
>Consider when Alice's preconditions work, but Bob's do not. Code that
>thinks it's calling Alice's code *must not* break when calling Bob's.
Okay, you've completely lost me now, because I don't know what you mean by
"work" in this context. Do you mean, "are met by the caller"? Or "are
syntactically valid"? Or...?
>Weakening pre-conditions means that Alice's pre-conditions must be
>tested as well: and Bob's code is run even if his pre-conditions fail.
Whaa? That can't be right. Weakening a precondition means that Bob's
preconditions should *replace* Alice's preconditions, if Bob has supplied
newer, weaker preconditions. Bob's code should *not* be run if Bob's
preconditions are not met.
Just to make sure we're not on completely different pages here, I'm
thinking this:
class AlicesClass:
def something(self):
"""pre: foo and bar"""
class BobsClass(AlicesClass):
def something(self):
"""pre: foo"""
That, to me, is weakening a precondition. Now, if what you're saying is
that Bob's code must work if *Alice's* preconditions are met, then that's
something different. What you're saying then, is that it's required that a
precondition in a subclass be logically implied by each of the
corresponding preconditions in the base classes.
That is certainly a reasonable requirement, but I don't see why the
language needs to enforce it, certainly not by running Bob's code even when
Bob's precondition fails! If you're going to enforce it, it should be
enforced by issuing an error for preconditions that aren't logically
implied by their superclass preconditions. Then you actually get some
benefit from the static checking. If you just run Bob's code, he has no
way to notice that he's violating Alice's contract, until his code keeps
breaking at runtime. (And then, he will almost certainly come to the
conclusion that the contract checker is broken!)
OTOH, if you accept Bob's precondition as he stated it, then he gets the
behavior he asked for. If this is a violation of Alice's contract, Bob's
users will either read the fact in his docs, or complain.
>The converse is also true: code that understands Bob's pre-conditions
>must not fail even if Alice's pre-conditions fail. This is tough to
>do with asserts, or with incompatible contract packages.
I still don't understand. If Bob has replaced Alice's method, what do her
preconditions have to do with it any more? If Bob's code *calls* Alice's
method, then the conditions of Alice's method presumably *do* need to apply
for that upcall, or else she has written them without enough indirection.
>I haven't made that clear in the PEP or the samples, and it needs to
>be clear, because it is the /only/ reason why contracts need to be in
>the language/standard runtime.
Yep, and I'm still totally not seeing why Alice and Bob have to use the
same mechanism. Alice could use method wrappers, Bob could use a
metaclass, and Carol could use assert statements, as far as I can see,
unless you are looking for static correctness checking. (In which case,
docstrings are the wrong place for this.)