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 <jam...@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.ris...@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+pyt...@gmail.com> > wrote: > >> On Sun, Sep 23, 2018 at 2:05 AM Marko Ristin-Kaufmann >> <marko.ris...@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@python.org > https://mail.python.org/mailman/listinfo/python-ideas > Code of Conduct: http://python.org/psf/codeofconduct/ > >
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/