Can we have assignment in a condition?
PythonPython Problem Overview
Is it possible to have assignment in a condition?
For ex.
if (a=some_func()):
# Use a
Python Solutions
Solution 1 - Python
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():
Solution 2 - Python
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 notation NAME := 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
Solution 3 - Python
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.
Solution 4 - Python
Not directly, per this old recipe of mine -- but as the recipe says it's easy to build the semantic equivalent, e.g. if you need to transliterate directly from a C-coded reference algorithm (before refactoring to more-idiomatic Python, of course;-). I.e.:
class DataHolder(object):
def __init__(self, value=None): self.value = value
def set(self, value): self.value = value; return value
def get(self): return self.value
data = DataHolder()
while data.set(somefunc()):
a = data.get()
# use a
BTW, a very idiomatic Pythonic form for your specific case, if you know exactly what falsish value somefunc
may return when it does return a falsish value (e.g. 0
), is
for a in iter(somefunc, 0):
# use a
so in this specific case the refactoring would be pretty easy;-).
If the return could be any kind of falsish value (0, None
, ''
, ...), one possibility is:
import itertools
for a in itertools.takewhile(lambda x: x, iter(somefunc, object())):
# use a
but you might prefer a simple custom generator:
def getwhile(func, *a, **k):
while True:
x = func(*a, **k)
if not x: break
yield x
for a in getwhile(somefunc):
# use a
Solution 5 - Python
Yes, but only from Python 3.8 and onwards.
PEP 572 proposes Assignment Expressions and has already been accepted.
Quoting the Syntax and semantics part of the PEP:
# Handle a matched regex
if (match := pattern.search(data)) is not None:
# Do something with match
# A loop that can't be trivially rewritten using 2-arg iter()
while chunk := file.read(8192):
process(chunk)
# Reuse a value that's expensive to compute
[y := f(x), y**2, y**3]
# Share a subexpression between a comprehension filter clause and its output
filtered_data = [y for x in data if (y := f(x)) is not None]
In your specific case, you will be able to write
if a := some_func():
# Use a
Solution 6 - Python
No. Assignment in Python is a statement, not an expression.
Solution 7 - Python
Thanks to Python 3.8 new feature it will be possible to do such a thing from this version, although not using =
but Ada-like assignment operator :=
. Example from the docs:
# Handle a matched regex
if (match := pattern.search(data)) is not None:
# Do something with match
Solution 8 - Python
You can define a function to do the assigning for you:
def assign(name, value):
import inspect
frame = inspect.currentframe()
try:
locals_ = frame.f_back.f_locals
finally:
del frame
locals_[name] = value
return value
if assign('test', 0):
print("first", test)
elif assign('xyz', 123):
print("second", xyz)
Solution 9 - Python
One of the reasons why assignments are illegal in conditions is that it's easier to make a mistake and assign True or False:
some_variable = 5
# This does not work
# if True = some_variable:
# do_something()
# This only works in Python 2.x
True = some_variable
print True # returns 5
In Python 3 True and False are keywords, so no risk anymore.
Solution 10 - Python
The assignment operator - also known informally as the the walrus operator - was created at 28-Feb-2018 in PEP572.
For the sake of completeness, I'll post the relevant parts so you can compare the differences between 3.7 and 3.8:
3.7
---
if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
test: or_test ['if' or_test 'else' test] | lambdef
test_nocond: or_test | lambdef_nocond
lambdef: 'lambda' [varargslist] ':' test
lambdef_nocond: 'lambda' [varargslist] ':' test_nocond
or_test: and_test ('or' and_test)*
and_test: not_test ('and' not_test)*
not_test: 'not' not_test | comparison
comparison: expr (comp_op expr)*
3.8
---
if_stmt: 'if' namedexpr_test ':' suite ('elif' namedexpr_test ':' suite)* ['else' ':' suite]
namedexpr_test: test [':=' test] <---- WALRUS OPERATOR!!!
test: or_test ['if' or_test 'else' test] | lambdef
or_test: and_test ('or' and_test)*
and_test: not_test ('and' not_test)*
not_test: 'not' not_test | comparison
comparison: expr (comp_op expr)*