PEP: 379 Title: Adding an Assignment Expression Version: $Revision$
Last-Modified: $Date$ Author: Jervis Whitley <jervisau@gmail.com>
Status: Withdrawn Type: Standards Track Content-Type: text/x-rst
Created: 14-Mar-2009 Python-Version: 2.7, 3.2 Post-History:

Abstract
========

This PEP adds a new assignment expression to the Python language to make
it possible to assign the result of an expression in almost any place.
The new expression will allow the assignment of the result of an
expression at first use (in a comparison for example).

Motivation and Summary
======================

Issue1714448 "if something as x:" \[1\]\_ describes a feature to allow
assignment of the result of an expression in an if statement to a name.
It supposed that the `as` syntax could be borrowed for this purpose.
Many times it is not the expression itself that is interesting, rather
one of the terms that make up the expression. To be clear, something
like this::

    if (f_result() == [1, 2, 3]) as res:

seems awfully limited, when this::

    if (f_result() as res) == [1, 2, 3]:

is probably the desired result.

Use Cases
=========

See the Examples section near the end.

Specification
=============

A new expression is proposed with the (nominal) syntax::

    EXPR -> VAR

This single expression does the following:

-   Evaluate the value of `EXPR`, an arbitrary expression;
-   Assign the result to `VAR`, a single assignment target; and
-   Leave the result of `EXPR` on the Top of Stack (TOS)

Here `->` or (`RARROW`) has been used to illustrate the concept that the
result of `EXPR` is assigned to `VAR`.

The translation of the proposed syntax is::

    VAR = (EXPR)
    (EXPR)

The assignment target can be either an attribute, a subscript or name::

    f() -> name[0]      # where 'name' exists previously.

    f() -> name.attr    # again 'name' exists prior to this expression.

    f() -> name

This expression should be available anywhere that an expression is
currently accepted.

All exceptions that are currently raised during invalid assignments will
continue to be raised when using the assignment expression. For example,
a `NameError` will be raised when in example 1 and 2 above if `name` is
not previously defined, or an `IndexError` if index 0 was out of range.

Examples from the Standard Library
==================================

The following two examples were chosen after a brief search through the
standard library, specifically both are from ast.py which happened to be
open at the time of the search.

Original::

    def walk(node):
        from collections import deque
        todo = deque([node])
        while todo:
            node = todo.popleft()
            todo.extend(iter_child_nodes(node))
            yield node

Using assignment expression::

    def walk(node):
        from collections import deque
        todo = deque([node])
        while todo:
            todo.extend(iter_child_nodes(todo.popleft() -> node))
            yield node

Original::

    def get_docstring(node, clean=True):
        if not isinstance(node, (FunctionDef, ClassDef, Module)):
            raise TypeError("%r can't have docstrings"
                                % node.__class__.__name__)
        if node.body and isinstance(node.body[0], Expr) and \
        isinstance(node.body[0].value, Str):
            if clean:
                import inspect
                return inspect.cleandoc(node.body[0].value.s)
            return node.body[0].value.s

Using assignment expression::

    def get_docstring(node, clean=True):
        if not isinstance(node, (FunctionDef, ClassDef, Module)):
            raise TypeError("%r can't have docstrings"
                                % node.__class__.__name__)
        if node.body -> body and isinstance(body[0] -> elem, Expr) and \
        isinstance(elem.value -> value, Str):
            if clean:
                import inspect
                return inspect.cleandoc(value.s)
            return value.s

Examples
========

The examples shown below highlight some of the desirable features of the
assignment expression, and some of the possible corner cases.

1.  Assignment in an if statement for use later::

    def expensive(): import time; time.sleep(1) return 'spam'

    if expensive() -\> res in ('spam', 'eggs'): dosomething(res)

2.  Assignment in a while loop clause::

    while len(expensive() -\> res) == 4: dosomething(res)

3.  Keep the iterator object from the for loop::

    for ch in expensive() -\> res: sell\_on\_internet(res)

4.  Corner case::

    for ch -\> please\_dont in expensive(): pass \# who would want to do
    this? Not I.

References
==========

.. \[1\] Issue1714448 "if something as x:", k0wax
http://bugs.python.org/issue1714448

Copyright
=========

This document has been placed in the public domain.

.. Local Variables: mode: indented-text indent-tabs-mode: nil
sentence-end-double-space: t fill-column: 70 coding: utf-8 End:
