[Python-Dev] Examples for PEP 572

Serhiy Storchaka storchaka at gmail.com
Tue Jul 3 17:37:10 EDT 2018


I like programming languages in which all are expressions (including 
function declarations, branching and loops) and you can use an 
assignment at any point, but Python is built on other ways, and I like 
Python too. PEP 572 looks violating several Python design principles. 
Python looks simple language, and this is its strong side. I believe 
most Python users are not professional programmers -- they are 
sysadmins, scientists, hobbyists and kids -- but Python is suitable for 
them because its clear syntax and encouraging good style of programming. 
In particularly mutating and non-mutating operations are separated. The 
assignment expression breaks this. There should be very good reasons for 
doing this. But it looks to me that all examples for PEP 572 can be 
written better without using the walrus operator.

> results = [(x, y, x/y) for x in input_data if (y := f(x)) > 0]

     results = [(x, y, x/y) for x in input_data for y in [f(x)] if y > 0]

> stuff = [[y := f(x), x/y] for x in range(5)]

     stuff = [[y, x/y] for x in range(5) for y in [f(x)]]

This idiom looks unusual for you? But this is a legal Python syntax, and 
it is not more unusual than the new walrus operator. This idiom is not 
commonly used because there is very little need of using above examples 
in real code. And I'm sure that the walrus operator in comprehension 
will be very rare unless PEP 572 will encourage writing complicated 
comprehensions. Most users prefer to write an explicit loop.

I want to remember that PEP 572 started from the discussion on 
Python-ideas which proposed a syntax for writing the following code as a 
comprehension:

     smooth_signal = []
     average = initial_value
     for xt in signal:
         average = (1-decay)*average + decay*xt
         smooth_signal.append(average)

Using the "for in []" idiom this can be written (if you prefer 
comprehensions) as:

     smooth_signal = [average
                      for average in [initial_value]
                      for x in signal
                      for average in [(1-decay)*average + decay*x]]

Try now to write this using PEP 572. The walrus operator turned to be 
less suitable for solving the original problem because it doesn't help 
to initialize the initial value.


Examples from PEP 572:

> # Loop-and-a-half
> while (command := input("> ")) != "quit":
>     print("You entered:", command)

The straightforward way:

     while True:
         command = input("> ")
         if command == "quit": break
         print("You entered:", command)

The clever way:

     for command in iter(lambda: input("> "), "quit"):
         print("You entered:", command)

> # Capturing regular expression match objects
> # See, for instance, Lib/pydoc.py, which uses a multiline spelling
> # of this effect
> if match := re.search(pat, text):
>     print("Found:", match.group(0))
> # The same syntax chains nicely into 'elif' statements, unlike the
> # equivalent using assignment statements.
> elif match := re.search(otherpat, text):
>     print("Alternate found:", match.group(0))
> elif match := re.search(third, text):
>     print("Fallback found:", match.group(0))

It may be more efficient to use a single regular expression which 
consists of multiple or-ed patterns marked as different groups. For 
example see the cute regex-based tokenizer in gettext.py:

> _token_pattern = re.compile(r"""
>         (?P<WHITESPACES>[ \t]+)                    | # spaces and horizontal tabs
>         (?P<NUMBER>[0-9]+\b)                       | # decimal integer
>         (?P<NAME>n\b)                              | # only n is allowed
>         (?P<PARENTHESIS>[()])                      |
>         (?P<OPERATOR>[-*/%+?:]|[><!]=?|==|&&|\|\|) | # !, *, /, %, +, -, <, >,
>                                                      # <=, >=, ==, !=, &&, ||,
>                                                      # ? :
>                                                      # unary and bitwise ops
>                                                      # not allowed
>         (?P<INVALID>\w+|.)                           # invalid token
>     """, re.VERBOSE|re.DOTALL)
> 
> def _tokenize(plural):
>     for mo in re.finditer(_token_pattern, plural):
>         kind = mo.lastgroup
>         if kind == 'WHITESPACES':
>             continue
>         value = mo.group(kind)
>         if kind == 'INVALID':
>             raise ValueError('invalid token in plural form: %s' % value)
>         yield value
>     yield ''

I have not found any code similar to the PEP 572 example in pydoc.py. It 
has different code:

> pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'
>                         r'RFC[- ]?(\d+)|'
>                         r'PEP[- ]?(\d+)|'
>                         r'(self\.)?(\w+))')
...
> start, end = match.span()
> results.append(escape(text[here:start]))
> 
> all, scheme, rfc, pep, selfdot, name = match.groups()
> if scheme:
>     url = escape(all).replace('"', '"')
>     results.append('<a href="%s">%s</a>' % (url, url))
> elif rfc:
>     url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)
>     results.append('<a href="%s">%s</a>' % (url, escape(all)))
> elif pep:
...

It doesn't look as a sequence of re.search() calls. It is more clear and 
efficient, and using the assignment expression will not make it better.

> # Reading socket data until an empty string is returned
> while data := sock.recv():
>     print("Received data:", data)

     for data in iter(sock.recv, b''):
         print("Received data:", data)

> if pid := os.fork():
>     # Parent code
> else:
>     # Child code

     pid = os.fork()
     if pid:
         # Parent code
     else:
         # Child code


It looks to me that there is no use case for PEP 572. It just makes 
Python worse.



More information about the Python-Dev mailing list