Can we have assignment in a condition?
UPDATE - Original answer is near the bottom
Python 3.8 will bring in PEP572
Abstract
This is a proposal for creating a way to assign to variables within an expression using the notationNAME := expr
. A new exception,TargetScopeError
is added, and there is one change to evaluation order.
https://lwn.net/Articles/757713/
The "PEP 572 mess" was the topic of a 2018 Python Language Summit session led by benevolent dictator for life (BDFL) Guido van Rossum. PEP 572 seeks to add assignment expressions (or "inline assignments") to the language, but it has seen a prolonged discussion over multiple huge threads on the python-dev mailing list—even after multiple rounds on python-ideas. Those threads were often contentious and were clearly voluminous to the point where many probably just tuned them out. At the summit, Van Rossum gave an overview of the feature proposal, which he seems inclined toward accepting, but he also wanted to discuss how to avoid this kind of thread explosion in the future.
https://www.python.org/dev/peps/pep-0572/#examples-from-the-python-standard-library
Examples from the Python standard library
site.py env_base is only used on these lines, putting its assignment on the if moves it as the "header" of the block.
Current:
env_base = os.environ.get("PYTHONUSERBASE", None) if env_base: return env_base
Improved:
if env_base := os.environ.get("PYTHONUSERBASE", None): return env_base _pydecimal.py
Avoid nested if and remove one indentation level.
Current:
if self._is_special: ans = self._check_nans(context=context) if ans: return ans
Improved:
if self._is_special and (ans := self._check_nans(context=context)): return ans
copy.py Code looks more regular and avoid multiple nested if. (See Appendix A for the origin of this example.)
Current:
reductor = dispatch_table.get(cls) if reductor: rv = reductor(x) else: reductor = getattr(x, "__reduce_ex__", None) if reductor: rv = reductor(4) else: reductor = getattr(x, "__reduce__", None) if reductor: rv = reductor() else: raise Error( "un(deep)copyable object of type %s" % cls)
Improved:
if reductor := dispatch_table.get(cls): rv = reductor(x) elif reductor := getattr(x, "__reduce_ex__", None): rv = reductor(4) elif reductor := getattr(x, "__reduce__", None): rv = reductor() else: raise Error("un(deep)copyable object of type %s" % cls) datetime.py
tz is only used for s += tz, moving its assignment inside the if helps to show its scope.
Current:
s = _format_time(self._hour, self._minute, self._second, self._microsecond, timespec) tz = self._tzstr() if tz: s += tz return s
Improved:
s = _format_time(self._hour, self._minute, self._second, self._microsecond, timespec) if tz := self._tzstr(): s += tz return s
sysconfig.py Calling fp.readline() in the while condition and calling .match() on the if lines make the code more compact without
making it harder to understand.
Current:
while True: line = fp.readline() if not line: break m = define_rx.match(line) if m: n, v = m.group(1, 2) try: v = int(v) except ValueError: pass vars[n] = v else: m = undef_rx.match(line) if m: vars[m.group(1)] = 0
Improved:
while line := fp.readline(): if m := define_rx.match(line): n, v = m.group(1, 2) try: v = int(v) except ValueError: pass vars[n] = v elif m := undef_rx.match(line): vars[m.group(1)] = 0
Simplifying list comprehensions A list comprehension can map and filter efficiently by capturing the condition:
results = [(x, y, x/y) for x in input_data if (y := f(x)) > 0]
Similarly, a subexpression can be reused within the main expression, by giving it a name on first use:
stuff = [[y := f(x), x/y] for x in range(5)]
Note that in both cases the variable y is bound in the containing scope (i.e. at the same level as results or stuff).
Capturing condition values Assignment expressions can be used to good effect in the header of an if or while statement:
# Loop-and-a-half while (command := 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)) # Reading socket data until an empty string is returned while data := sock.recv(8192): print("Received data:", data)
Particularly with the while loop, this can remove the need to have an infinite loop, an assignment, and a condition. It also creates a smooth parallel between a loop which simply uses a function call as its condition, and one which uses that as its condition but also uses the actual value.
Fork An example from the low-level UNIX world:
if pid := os.fork(): # Parent code else: # Child code
Original answer
http://docs.python.org/tutorial/datastructures.html
Note that in Python, unlike C, assignment cannot occur inside expressions. C programmers may grumble about this, but it avoids a common class of problems encountered in C programs: typing = in an expression when == was intended.
also see:
http://effbot.org/pyfaq/why-can-t-i-use-an-assignment-in-an-expression.htm
Why not try it out?
>>> def some_func():
... return 2
...
>>> if (a = some_func()):
File "<stdin>", line 1
if (a = some_func()):
^
SyntaxError: invalid syntax
So, no.
Update: This is possible (with different syntax) in Python 3.8
if a := some_func():
Nope, the BDFL didn't like that feature.
From where I sit, Guido van Rossum, "Benevolent Dictator For Life”, has fought hard to keep Python as simple as it can be. We can quibble with some of the decisions he's made -- I'd have preferred he said 'No' more often. But the fact that there hasn't been a committee designing Python, but instead a trusted "advisory board", based largely on merit, filtering through one designer's sensibilities, has produced one hell of a nice language, IMHO.