I could emulate the "where" semantics as described by Steven using the class statement and eval :
I guess it is useful if someone want to try refactoring a piece of "real world" code, and see if it really feels better - then we could try to push for the "where" syntax, which I kinda like: (This emulation requires the final expression to be either a string to be eval'ed, or a lambda function - but then some namespace retrieval and parameter matching would require a bit more code on the metaclass): class Where(type): def __new__(metacls, name, bases, namespace, *, expr=''): return eval(expr, namespace) def sqroot(n): class roots(expr="[(-b + r)/ (2 * a) for r in (+ delta **0.5, - delta ** 0.5) ]", metaclass=Where): a, b, c = n delta = b ** 2 - 4 * a * c return roots On 21 June 2017 at 10:31, Brice PARENT <cont...@brice.xyz> wrote: > I might not have understood it completely, but I think the use cases would > probably better be splitted into two categories, each with a simple > solution (simple in usage at least): > > *When we just want a tiny scope for a variable:* > > Syntax: > > with [assignment]: > # use of the variable > > # assigned variable is now out of scope > > Examples: > > with b = a + 1: > y = b + 2 > > # we can use y here, but not b > > or > > with delta = lambda a, b, c: b**2 - 4 * a * c: > x1 = (- b - math.sqrt(delta(a, b, c))) / (2 * a) > x2 = (- b + math.sqrt(delta(a, b, c))) / (2 * a) > > # delta func is now out of scope and has been destroyed > > We don't keep unnecessarily some variables, as well as we don't risk any > collision with outer scopes (and we preserve readability by not declaring a > function for that). > > It would probably mean the assignment operator should behave differently > than it does now which could have unexpected (to me) implications. It would > have to support both __enter__ and __exit__ methods, but I don't know if > this makes any sense. I don't know if with a + 1 as b: would make a > better sense or be a side-effect or special cases hell. > > *When we want to simplify a comprehension:* > > (although it would probably help in many other situations) > > Syntax: > > prepare_iterable(sequence, *functions) > > which creates a new iterable containing tuples like (element, > return_of_function_1, return_of_function_2, ...) > > Examples: > > [m(spam)[eggs] for _, m in prepare_iterable(sequence, lambda obj: > obj[0].field.method) if m] > > or, outside of a comprehension: > > sequence = [0, 1, 5] > prepare_iterable(sequence, lambda o: o * 3, lambda o: o + 1) > # -> [(0, 0, 1), (1, 3, 2), (5, 15, 6)] > The "prepare_iterable" method name might or might not be the right word > to use. But English not being my mother language, I'm not the right person > to discuss this... > It would be a function instead of a method shared by all iterables to be > able to yield the elements instead of processing the hole set of data right > from the start. > This function should probably belong to the standard library but probably > not in the general namespace. > > -- Brice > > Le 17/06/17 à 12:27, Steven D'Aprano a écrit : > > On Sat, Jun 17, 2017 at 09:03:54AM +0200, Sven R. Kunze wrote: > > On 17.06.2017 02:27, Steven D'Aprano wrote: > > I think this is somewhat similar to a suggestion of Nick Coghlan's. One > possible syntax as a statement might be: > > y = b + 2 given: > b = a + 1 > > Just to get this right:this proposal is about reversing the order of > chaining expressions? > > Partly. Did you read the PEP? > https://www.python.org/dev/peps/pep-3150/ > > I quote: > > The primary motivation is to enable a more declarative style of > programming, where the operation to be performed is presented to the > reader first, and the details of the necessary subcalculations are > presented in the following indented suite. > [...] > A secondary motivation is to simplify interim calculations in module > and class level code without polluting the resulting namespaces. > > It is not *just* about reversing the order, it is also about avoiding > polluting the current namespace (global, or class) with unnecessary > temporary variables. This puts the emphasis on the important part of the > expression, not the temporary/implementation variables: > > page = header + body + footer where: > header = ... > body = ... > footer = ... > > There is prior art: the "where" and "let" clauses in Haskell, as well as > mathematics, where it is very common to defer the definition of > temporary variables until after they are used. > > > > Instead of: > > b = a + 1 > c = b + 2 > > we could write it in reverse order: > > c = b + 2 given/for: > b = a + 1 > > > Right. But of course such a trivial example doesn't demonstrate any > benefit. This might be a better example. > > Imagine you have this code, where the regular expression and the custom > sort function are used in one place only. Because they're only used > *once*, we don't really need them to be top-level global names, but > currently we have little choice. > > regex = re.compile(r'.*?(\d*).*') > > def custom_sort(string): > mo = regex.match(string) > ... some implementation > return key > > # Later > results = sorted(some_strings, key=custom_sort) > > # Optional > del custom_sort, regex > > > Here we get the order of definitions backwards: the thing we actually > care about, results = sorted(...), is defined last, and mere > implementation details are given priority as top-level names that > either hang around forever, or need to be explicitly deleted. > > Some sort of "where" clause could allow: > > results = sorted(some_strings, key=custom_sort) where: > regex = re.compile(r'.*?(\d*).*') > > def custom_sort(string): > mo = regex.match(string) > ... some implementation > return key > > > If this syntax was introduced, editors would soon allow you to fold the > "where" block and hide it. The custom_sort and regex names would be > local to the where block and the results = ... line. > > Another important use-case is comprehensions, where we often have to > repeat ourselves: > > [obj[0].field.method(spam)[eggs] for obj in sequence if obj[0].field.method] > > One work around: > > [m(spam)[eggs] for m in [obj[0].field.method for obj in sequence] if m] > > But perhaps we could do something like: > > [m(spam)[eggs] for obj in sequence where m = obj[0].field.method if m] > > or something similar. > > > > > If so, I don't know if it just complicates the language with a feature > which does not save writing nor reading > > It helps to save reading, by pushing less-important implementation > details of an expression into an inner block where it is easy to ignore > them. Even if you don't have an editor which does code folding, it is > easy to skip over an indented block and just read the header line, > ignoring the implementation. We already do this with classes, functions, > even loops: > > class K: > ... implementation of K > > def func(arg): > ... implementation of func > > for x in seq: > ... implementation of loop body > > page = header + body + footer where: > ... implementation of page > > > As a general rule, any two lines at the same level of indentation are > read as being of equal importance. When we care about the implementation > details, we "drop down" into the lower indentation block. But when > skimming the code for a high-level overview, we skip the details of > indented blocks and focus only on the current level: > > class K: > def func(arg): > for x in seq: > page = header + body + footer where: > > > (That's why editors often provide code folding, to hide the details of > an indented block. But even without that feature, we can do it in our > own head, although not as effectively.) > > > > > > > _______________________________________________ > 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/