[Python-ideas] Inline assignments using "given" clauses

Cameron Simpson cs at cskk.id.au
Sat May 12 20:53:50 EDT 2018


On 13May2018 08:55, Chris Angelico <rosuav at gmail.com> wrote:
>On Sun, May 13, 2018 at 8:47 AM, Juancarlo Añez <apalala at gmail.com> wrote:
>>
>>> My main point here is that "with" works as well as "given" in this form
>>> from an English prose point of view.
>>
>>
>> +1 for "with...as", -1 for ":="
>>
>> About affecting existing contexts, it seems that "with..as" would create a
>> new context just for the expression, and the control statement it is
>> embedded in, similar to what the current "with" statement does. These are
>> semantics that are really easy to explain.
>
>The trouble with every variant involving 'with' is that the semantics
>LOOK similar, but are subtly different. The current 'with' statement
>doesn't create a subscope; the only "context" it creates is regarding
>the resource represented by the context manager. For instance, opening
>a file in a 'with' block will close the file at the end of the block -
>but you still have a (closed) file object. Using "with... as" for name
>bindings wouldn't call __enter__ or __exit__, so it won't create that
>kind of context; and whether it creates a subscope for the variable or
>not, it's not going to match the 'with' statement.

For myself, I'm not a fan of a narrow scope. I'd be happy with an inline 
assignment landing in the function scope (or whatever the current innermost 
scope is). Like normal assignments. In how many other places does Python adopt 
a narrower scope than the function/method/class/module?

Consider:

  r = re.compile("bah(baz)")
  if m given m = r.match("foo barbaz"):
    thing = m.group(1)

I _want_ to access "m" after the expression.

My other dislike of narrow scopes is shadowing. I had an unpleasant experience 
last year working in Go, which has its own ":=" assignment. While Go's := 
semantics are a little different to the assignment expressions being considered 
here, it has an IMO dangerous shadowing effect.

I'm going to digress into an explaination of this issue in Go here because I 
want to highlight my dislike of easy accidental shadowing, which is a problem 
in many areas, and I think that conflating inline assignment with an implied 
narrow scope in Python would make this worse.

In Go you can declare variables 2 ways:

  var ok = True

and:

  ok := True

Also, because Go doesn't use exceptions a common idiom is to return a 
success/fail value and the task's result:

  ok, result := do_something(...)

That handily declares "ok" and "result" and gives them values.

Because the ":=" is so convenient, you might often declare variables as they 
get used:


  ok, result1 := do_something()
  ok, result2 := do_something_else()

and so on. You're allowed to mix existing names with new (undeclared) names 
provided there's at least one new name left of the ":=".

So far this is all just ordinary assignments, with convenient declaration 
included. But consider the function that calls other functions:

  func thing_outer(x) {
      ok, result1 := do_something()
      if ok {
        ok, result2 := do_something_else()
        if ok {
          fmt.Println("ok!")
        }
      }
      return ok, result
  }

That's how it might go in Pythonic form. But Go lets you preceed the test 
condition with a statement, somewhat like the C "for (setup values; test; 
advance)" form:

  for (i=0; i<10; i++)

So you may wish to write the function like this:

  func thing_outer(x) {
      if ok, result1 := do_something(); ok {
        if ok, result2 := do_something_else(); ok {
          fmt.Println("ok!")
        }
      }
      return ok, result
  }

and here is where the scoping causes a disaster.

In Go, the declarations _within_ the "if" statement have the "if" statement as 
their scope. In particular, the inner "ok" shadows the outer "ok", such that an 
inner failure (setting "ok" to false) doesn't affect the outer "ok" which was 
true. And so the overall function returns apparent success.

And that is entirely enabled by the implied scoping in the "if" statement.

The above example is subtly wrong because I'm doing this from memory, but I had 
a very simple real world example bite me this way and it was a PITA to debug 
because the effect was so surprising. So much so that from then on I pretty 
much eschewed the ":=" declaration as a dangerous construct and went with "var" 
all the time, effectively giving me _one_ scope within any function.

Cheers,
Cameron Simpson <cs at cskk.id.au>


More information about the Python-ideas mailing list