Hi Marko, > 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))
Yes, though if we’re talking syntax using kwargs would probably be better. Using “P” instead of “_”: (I agree that _ smells of ignored arguments) @snapshot(some_identifier=lambda P: ..., some_identifier2=lambda P: ...) Kwargs has the advantage that you can extend multiple lines without repeating @snapshot, though many lines of @capture would probably be more intuitive since each decorator captures one variable. > Why uppercase "P" and not lowercase (uppercase implies a constant for me)? To me, the capital letters are more prominent and explicit- easier to see when reading code. It also implies its a constant for you- you shouldn’t be modifying it, because then you’d be interfering with the function itself. Side node: maybe it would be good to have an @icontract.nomutate (probably use a different name, maybe @icontract.readonly) that makes sure a method doesn’t mutate its own __dict__ (and maybe the __dict__ of the members of its __dict__). It wouldn’t be necessary to put the decorator on every read only function, just the ones your worried might mutate. Maybe a @icontract.nomutate(param=“paramname”) that ensures the __dict__ of all members of the param name have the same equality or identity before and after. The semantics would need to be worked out. > On Sep 26, 2018, at 8:58 AM, Marko Ristin-Kaufmann <marko.ris...@gmail.com> > wrote: > > 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/