> The problem with readability might be easier to solve than I thought, and 
> your pointer to coconut gave me the idea. What if we make a utility that 
> takes the python source code, examines the decorators pre/post/inv (or 
> whatever we call them) and transforms them back and forth from/to valid 
> python code?
> 
> Pretty much any IDE has after load / before save handlers. When you load a 
> python file, you'd transform it from less readable python code, write using a 
> concise form and when you save it, it's transformed back to valid python.
Hmm yes. Look at my previous email- the proposed syntax there does not require 
changing or transforming Python code. It’s missing many magic methods, but it 
would work something like this:

@snapshot(some_identifier=P.self.another_property + 10)

would internally turn into something like this: lambda P: 
P.self.another_property + 10

Maybe it’s possible to modify PyCharm parsing by altering its Python grammar 
file?

Sent from my iPhone

> On Sep 28, 2018, at 10:27 AM, Marko Ristin-Kaufmann <marko.ris...@gmail.com> 
> wrote:
> 
> Hi James,
> 
> The problem with readability might be easier to solve than I thought, and 
> your pointer to coconut gave me the idea. What if we make a utility that 
> takes the python source code, examines the decorators pre/post/inv (or 
> whatever we call them) and transforms them back and forth from/to valid 
> python code?
> 
> Pretty much any IDE has after load / before save handlers. When you load a 
> python file, you'd transform it from less readable python code, write using a 
> concise form and when you save it, it's transformed back to valid python.
> 
> Then we need to pick the python form that is easiest to implement (and still 
> readable enough for, say, code reviews on github), but writing and reading 
> the contracts in the code would be much more pleasant.
> 
> As long as the "readable" form has also valid python syntax, the tool can be 
> implemented with ast module.
> 
> For example:
> @snapshot(some_identifier=self.another_property + 10)
> @post(self.some_property => old.some_identifier > 100)
> 
> would transform into 
> @snapshot(lambda P, some_identifier: P.self.another_property + 10)
> @post(lambda O, P: not self.some_property and O.some_identifier > 100)
> 
> Cheers,
> Marko
> 
>> On Fri, 28 Sep 2018 at 03:49, Marko Ristin-Kaufmann <marko.ris...@gmail.com> 
>> wrote:
>> Hi James,
>> 
>> I still have a feeling that most developers would like to store the state in 
>> many different custom ways. I see also thunk and snapshot with wrapper 
>> objects to be much more complicated to implement and maintain; I'm thinking 
>> mostly about all the edge cases which we would not be able to cover (and how 
>> complex that would be to cover them). Then the linters need also to work 
>> around such wrappers... It might also scare users off since it looks like 
>> too much magic. Another concern I also have is that it's probably very hard 
>> to integrate these wrappers with mypy later -- but I don't really have a 
>> clue about that, only my gut feeling?
>> 
>> What about we accepted to repeat "lambda P, " prefix, and have something 
>> like this:
>> 
>> @snapshot(
>>   lambda P, some_name: len(P.some_property),
>>   lambda P, another_name: hash(P.another_property)
>> )
>> 
>> It's not too verbose for me and you can still explain in three-four 
>> sentences what happens below the hub in the library's docs.  A 
>> pycharm/pydev/vim/emacs plugins could hide the verbose parts. 
>> 
>> I performed a small experiment to test how this solution plays with pylint 
>> and it seems OK that arguments are not used in lambdas.
>> 
>> Cheers,
>> Marko
>> 
>> 
>>> On Thu, 27 Sep 2018 at 12:27, James Lu <jam...@gmail.com> wrote:
>>> Why couldn’t we record the operations  done to a special object and replay 
>>> them?
>>> 
>>>>>> 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).
>>> 
>>> 
>>> from icontract import snapshot, P, thunk
>>> @snapshot(some_identifier=P.self.some_method(P.some_argument.some_attr))
>>> 
>>> P is an object of our own type, let’s call the type MockP. MockP returns 
>>> new MockP objects when any operation is done to it. MockP * MockP = MockP. 
>>> MockP.attr = MockP. MockP objects remember all the operations done to them, 
>>> and allow the owner of a MockP object to re-apply the same operations 
>>> 
>>> “thunk” converts a function or object or class to a MockP object, storing 
>>> the function or object for when the operation is done. 
>>> 
>>> thunk(function)(<MockP expression>)
>>> 
>>> Of course, you could also thunk objects like so: thunk(3) * P.number. 
>>> (Though it might be better to keep the 3 after P.number in this case so 
>>> P.number’s __mult__ would be invoked before 3’s __mult__ is invokes.
>>> 
>>> 
>>> In most cases, you’d save any operations that can be done on a copy of the 
>>> data as generated by @snapshot in @postcondiion. thunk is for rare 
>>> scenarios where 1) it’s hard to capture the state, for example an object 
>>> that manages network state (or database connectivity etc) and whose stage 
>>> can only be read by an external classmethod  2) you want to avoid using 
>>> copy.deepcopy.
>>> 
>>> I’m sure there’s some way to override isinstance through a meta class or 
>>> dunder subclasshook.
>>> 
>>> I suppose this mocking method could be a shorthand for when you don’t need 
>>> the full power of a lambda. It’s arguably more succinct and readable, 
>>> though YMMV.
>>> 
>>> I look forward to reading your opinion on this and any ideas you might have.
>>> 
>>>> On Sep 26, 2018, at 3:56 PM, James Lu <jam...@gmail.com> wrote:
>>>> 
>>>> 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/

Reply via email to