Can we have assignment in a condition?

Python

Python 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)*

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionVishalView Question on Stackoverflow
Solution 1 - PythonJason HallView Answer on Stackoverflow
Solution 2 - PythonJohn La RooyView Answer on Stackoverflow
Solution 3 - PythonKevin LittleView Answer on Stackoverflow
Solution 4 - PythonAlex MartelliView Answer on Stackoverflow
Solution 5 - PythontimgebView Answer on Stackoverflow
Solution 6 - PythonIgnacio Vazquez-AbramsView Answer on Stackoverflow
Solution 7 - PythonJean-François FabreView Answer on Stackoverflow
Solution 8 - PythonWillem HengeveldView Answer on Stackoverflow
Solution 9 - Pythonuser2979916View Answer on Stackoverflow
Solution 10 - PythonBPLView Answer on Stackoverflow