[Python-ideas] "old" values in postconditions
Marko Ristin-Kaufmann
marko.ristin at gmail.com
Wed Sep 26 08:58:40 EDT 2018
Hi James,
Actually, following on #A4, you could also write those as multiple
decorators:
@snpashot(lambda _, some_identifier: some_func(_, some_argument.some_attr)
@snpashot(lambda _, other_identifier: other_func(_.self))
Am I correct?
"_" looks a bit hard to read for me (implying ignored arguments).
Why uppercase "P" and not lowercase (uppercase implies a constant for me)?
Then "O" for "old" and "P" for parameters in a condition:
@post(lambda O, P: ...)
?
It also has the nice property that it follows both the temporal and the
alphabet order :)
On Wed, 26 Sep 2018 at 14:30, James Lu <jamtlu at gmail.com> wrote:
> I still prefer snapshot, though capture is a good name too. We could use
> generator syntax and inspect the argument names.
>
> Instead of “a”, perhaps use “_”. Or maybe use “A.”, for arguments. Some
> people might prefer “P” for parameters, since parameters sometimes means
> the value received while the argument means the value passed.
>
> (#A1)
>
> from icontract import snapshot, __
> @snapshot(some_func(_.some_argument.some_attr) for some_identifier, _ in
> __)
>
> Or (#A2)
>
> @snapshot(some_func(some_argument.some_attr) for some_identifier, _,
> some_argument in __)
>
> —
> Or (#A3)
>
> @snapshot(lambda some_argument,_,some_identifier:
> some_func(some_argument.some_attr))
>
> Or (#A4)
>
> @snapshot(lambda _,some_identifier: some_func(_.some_argument.some_attr))
> @snapshot(lambda _,some_identifier, other_identifier:
> some_func(_.some_argument.some_attr), other_func(_.self))
>
> I like #A4 the most because it’s fairly DRY and avoids the extra
> punctuation of
>
> @capture(lambda a: {"some_identifier": some_func(a.some_argument.some_attr)})
>
>
> On Sep 26, 2018, at 12:23 AM, Marko Ristin-Kaufmann <
> marko.ristin at gmail.com> wrote:
>
> Hi,
>
> Franklin wrote:
>
>> The name "before" is a confusing name. It's not just something that
>> happens before. It's really a pre-`let`, adding names to the scope of
>> things after it, but with values taken before the function call. Based
>> on that description, other possible names are `prelet`, `letbefore`,
>> `predef`, `defpre`, `beforescope`. Better a name that is clearly
>> confusing than one that is obvious but misleading.
>
>
> James wrote:
>
>> I suggest that instead of “@before” it’s “@snapshot” and instead of “old”
>> it’s “snapshot”.
>
>
> I like "snapshot", it's a bit clearer than prefixing/postfixing verbs with
> "pre" which might be misread (*e.g., *"prelet" has a meaning in Slavic
> languages and could be subconsciously misread, "predef" implies to me a pre-
> *definition* rather than prior-to-definition , "beforescope" is very
> clear for me, but it might be confusing for others as to what it actually
> refers to ). What about "@capture" (7 letters for captures *versus *8 for
> snapshot)? I suppose "@let" would be playing with fire if Python with
> conflicting new keywords since I assume "let" to be one of the candidates.
>
> Actually, I think there is probably no way around a decorator that
> captures/snapshots the data before the function call with a lambda (or even
> a separate function). "Old" construct, if we are to parse it somehow from
> the condition function, would limit us only to shallow copies (and be
> complex to implement as soon as we are capturing out-of-argument values
> such as globals *etc.)*. Moreove, what if we don't need shallow copies? I
> could imagine a dozen of cases where shallow copy is not what the
> programmer wants: for example, s/he might need to make deep copies, hash or
> otherwise transform the input data to hold only part of it instead of
> copying (*e.g., *so as to allow equality check without a double copy of
> the data, or capture only the value of certain property transformed in some
> way).
>
> I'd still go with the dictionary to allow for this extra freedom. We could
> have a convention: "a" denotes to the current arguments, and "b" denotes
> the captured values. It might make an interesting hint that we put "b"
> before "a" in the condition. You could also interpret "b" as "before" and
> "a" as "after", but also "a" as "arguments".
>
> @capture(lambda a: {"some_identifier": some_func(a.some_argument.some_attr)})
> @post(lambda b, a, result: b.some_identifier > result + a.another_argument.another_attr)
> def some_func(some_argument: SomeClass, another_argument: AnotherClass) -> SomeResult:
> ...
>
> "b" can be omitted if it is not used. Under the hub, all the arguments to
> the condition would be passed by keywords.
>
> In case of inheritance, captures would be inherited as well. Hence the
> library would check at run-time that the returned dictionary with captured
> values has no identifier that has been already captured, and the linter
> checks that statically, before running the code. Reading values captured in
> the parent at the code of the child class might be a bit hard -- but that
> is case with any inherited methods/properties. In documentation, I'd list
> all the captures of both ancestor and the current class.
>
> I'm looking forward to reading your opinion on this and alternative
> suggestions :)
> Marko
>
> On Tue, 25 Sep 2018 at 18:12, Franklin? Lee <leewangzhong+python at gmail.com>
> wrote:
>
>> On Sun, Sep 23, 2018 at 2:05 AM Marko Ristin-Kaufmann
>> <marko.ristin at gmail.com> wrote:
>> >
>> > Hi,
>> >
>> > (I'd like to fork from a previous thread, "Pre-conditions and
>> post-conditions", since it got long and we started discussing a couple of
>> different things. Let's discuss in this thread the implementation of a
>> library for design-by-contract and how to push it forward to hopefully add
>> it to the standard library one day.)
>> >
>> > For those unfamiliar with contracts and current state of the discussion
>> in the previous thread, here's a short summary. The discussion started by
>> me inquiring about the possibility to add design-by-contract concepts into
>> the core language. The idea was rejected by the participants mainly because
>> they thought that the merit of the feature does not merit its costs. This
>> is quite debatable and seems to reflect many a discussion about
>> design-by-contract in general. Please see the other thread, "Why is
>> design-by-contract not widely adopted?" if you are interested in that
>> debate.
>> >
>> > We (a colleague of mine and I) decided to implement a library to bring
>> design-by-contract to Python since we don't believe that the concept will
>> make it into the core language anytime soon and we needed badly a tool to
>> facilitate our work with a growing code base.
>> >
>> > The library is available at http://github.com/Parquery/icontract. The
>> hope is to polish it so that the wider community could use it and once the
>> quality is high enough, make a proposal to add it to the standard Python
>> libraries. We do need a standard library for contracts, otherwise projects
>> with conflicting contract libraries can not integrate (e.g., the contracts
>> can not be inherited between two different contract libraries).
>> >
>> > So far, the most important bits have been implemented in icontract:
>> >
>> > Preconditions, postconditions, class invariants
>> > Inheritance of the contracts (including strengthening and weakening of
>> the inherited contracts)
>> > Informative violation messages (including information about the values
>> involved in the contract condition)
>> > Sphinx extension to include contracts in the automatically generated
>> documentation (sphinx-icontract)
>> > Linter to statically check that the arguments of the conditions are
>> correct (pyicontract-lint)
>> >
>> > We are successfully using it in our code base and have been quite happy
>> about the implementation so far.
>> >
>> > There is one bit still missing: accessing "old" values in the
>> postcondition (i.e., shallow copies of the values prior to the execution of
>> the function). This feature is necessary in order to allow us to verify
>> state transitions.
>> >
>> > For example, consider a new dictionary class that has "get" and "put"
>> methods:
>> >
>> > from typing import Optional
>> >
>> > from icontract import post
>> >
>> > class NovelDict:
>> > def length(self)->int:
>> > ...
>> >
>> > def get(self, key: str) -> Optional[str]:
>> > ...
>> >
>> > @post(lambda self, key, value: self.get(key) == value)
>> > @post(lambda self, key: old(self.get(key)) is None and
>> old(self.length()) + 1 == self.length(),
>> > "length increased with a new key")
>> > @post(lambda self, key: old(self.get(key)) is not None and
>> old(self.length()) == self.length(),
>> > "length stable with an existing key")
>> > def put(self, key: str, value: str) -> None:
>> > ...
>> >
>> > How could we possible implement this "old" function?
>> >
>> > Here is my suggestion. I'd introduce a decorator "before" that would
>> allow you to store whatever values in a dictionary object "old" (i.e. an
>> object whose properties correspond to the key/value pairs). The "old" is
>> then passed to the condition. Here is it in code:
>> >
>> > # omitted contracts for brevity
>> > class NovelDict:
>> > def length(self)->int:
>> > ...
>> >
>> > # omitted contracts for brevity
>> > def get(self, key: str) -> Optional[str]:
>> > ...
>> >
>> > @before(lambda self, key: {"length": self.length(), "get":
>> self.get(key)})
>> > @post(lambda self, key, value: self.get(key) == value)
>> > @post(lambda self, key, old: old.get is None and old.length + 1 ==
>> self.length(),
>> > "length increased with a new key")
>> > @post(lambda self, key, old: old.get is not None and old.length ==
>> self.length(),
>> > "length stable with an existing key")
>> > def put(self, key: str, value: str) -> None:
>> > ...
>> >
>> > The linter would statically check that all attributes accessed in "old"
>> have to be defined in the decorator "before" so that attribute errors would
>> be caught early. The current implementation of the linter is fast enough to
>> be run at save time so such errors should usually not happen with a
>> properly set IDE.
>> >
>> > "before" decorator would also have "enabled" property, so that you can
>> turn it off (e.g., if you only want to run a postcondition in testing). The
>> "before" decorators can be stacked so that you can also have a more
>> fine-grained control when each one of them is running (some during test,
>> some during test and in production). The linter would enforce that before's
>> "enabled" is a disjunction of all the "enabled"'s of the corresponding
>> postconditions where the old value appears.
>> >
>> > Is this a sane approach to "old" values? Any alternative approach you
>> would prefer? What about better naming? Is "before" a confusing name?
>>
>> The dict can be splatted into the postconditions, so that no special
>> name is required. This would require either that the lambdas handle
>> **kws, or that their caller inspect them to see what names they take.
>> Perhaps add a function to functools which only passes kwargs that fit.
>> Then the precondition mechanism can pass `self`, `key`, and `value` as
>> kwargs instead of args.
>>
>> For functions that have *args and **kwargs, it may be necessary to
>> pass them to the conditions as args and kwargs instead.
>>
>> The name "before" is a confusing name. It's not just something that
>> happens before. It's really a pre-`let`, adding names to the scope of
>> things after it, but with values taken before the function call. Based
>> on that description, other possible names are `prelet`, `letbefore`,
>> `predef`, `defpre`, `beforescope`. Better a name that is clearly
>> confusing than one that is obvious but misleading.
>>
>> By the way, should the first postcondition be `self.get(key) is
>> value`, checking for identity rather than equality?
>>
> _______________________________________________
> 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/20180926/48a4b159/attachment-0001.html>
More information about the Python-ideas
mailing list