Use cases for "and" in variable assignment - python

I discovered today that you can use and in variable assignment, similarly to how or is used. I rarely come across using or in this way, but have never even heard of people using and this way. Is this too obscure of a feature to recommend using or are there some concrete use cases where it helps with code clarity or brevity?
a = 1
b = 3
# c is equal to b unless a or b is False; then c is equal to the "False" value. False may be 0, [], False, etc.
c = a and b
print(f'a = {a}, b = {b}, c = {c}')
>>>a = 1, b = 3, c = 3
d = 1
e = 5
# f is equal to d unless d is False; then f is equal to e. Again, "False" may be 0, [], False, etc.
f = d or e
print(f'd = {d}, e = {e}, f = {f}')
>>>d = 1, e = 5, f = 1
There seems to be a weird inconsistency where it's obviously fine to use operators to evaluate a condition and set a variable to the truthiness of that condition (e.g. g = h > i or j = k is l etc).
However, and seems to be an exception. Instead of evaluating the condition right of the assignment, the variable is assigned according to the rule described in the above comment. Why doesn't c = a and b just evaluate to True or False depending on both a and b having truthy values? (The above example would evaluate to True)
Thanks

Short circuiting with and is a convenient way to express your intent with little code (a desirable goal indeed).
Consider this initialization, and what you would have to do if user wasn't known to be non-null.
name = user and user.name
Sure, a ternary would be a similar oneliner
name = user.name if user else None
but is it that readable?
Finally, when chaining multiple getters using the short-circuiting and truly starts to save your sanity.
coords = user and user.location and user.location.coords
Use or to provide a better default instead of None when you know for sure it wouldn't be a problem to override a falsey value.
name = user and user.name or 'Unnamed'

Basically what's going on here as has been stated is Short Circuit Evaluation. When the first value in an and evaluates to True then it returns the second value vice returning the False value. Consider these statements
>>> 1 and 0
0
>>> 1 and 3
3
>>> 0 and 1
0
>>> False and "Text"
False
"Text" and False
False

Your question: Why doesn't c = a and b just evaluate to True or False depending on both a and b having truthy values?
According the the Python manual, the definition of a and b is:
if a is false, then a, else b
So in your particular case when a has no side effects, the above translated into actual Python would be equivalent to:
c = a if not a else b
But it is not generally true that a has no side effects. So the difference between c = a and b and c = a if not a else b is as follows:
With a and b, if a is true then b will never be evaluated and a is evaluated once.
With c = a if not a else b, as before if a is true then b will never be evaluated but a will be evaluated a second time, which could be an issue if a does have side effects.

Related

Checking the inequality of several variables in python without knowing their value [duplicate]

This question already has answers here:
Test if all N variables are different
(4 answers)
Closed 2 years ago.
I have four string variables: a,b,c,d. I can't know their values (they are assigned randomly), and I don't care. I only need to make sure that every single on of them is different from others and there are not two variables with the same value. I tried
if a != b != c != d:
return true
but it doesn't work. What should I do?
You can use the naive approach:
if a != b and a != c and a != d and b != c and b != d and c != d:
# do something
Or you can make use of the fact that a set cannot hold the same hashable value twice:
if len(set([a, b, c, d])) == 4:
# do something
Or, a shorter way of writing the same:
if len({a, b, c, d}) == 4:
# do something
The reason your code doesn't work is because you're really doing this:
if a != b and b != c and c != d:
return true
So, it only checks part of what you need to check, it checks if all of the values directly following each other are equal, but not whether values more than one position apart are equal.
Do this?
assert len({a, b, c, d}) == 4 # set of 4 distinct elements should be 4
if len({a, b, c, d}) == 4:
return True
The reason your conditions don’t work
>>> 1 != 2 != 1
True
From the above example, 1 != 2 and 2 != 1, hence passing the condition. It doesn’t check your first and third variable's equality.
You cannot cannot chain comparisons like this. Below is one way to do it using if statements, although it is messy.
if (a != b and a != c and a != d
and b != c and b != d and c !=d):
return True
A nifty approach (albeit with some minor speed and space costs) is the following:
things_to_see_if_unique = [a, b, c, d]
if len(things_to_see_if_unique) == len(set(things_to_see_if_unique)):
return True

Why do these shorthands not work with each other?

I was wondering how to take advantage of shorthand notation of if-else and += in Python of this simple expression:
I tried to set brackets everywhere and changed += to *= which didn't change the situation of course.
This works as expected:
a, b = 0, True
for i in range(123):
if b == True:
a = a + 1
Still working as expected, trying shorthand of if-else led me to:
a, b = 0, True
for i in range(123):
a = a + 1 if b == True else a
Finally the attempt to write:
a, b = 0, True
for i in range(123):
a += 1 if b == True else a:
fails and surprisingly I get pretty quickly huge integers for a
Moreover I'd really like something more shorthanded, e.g.:
a, b = 0, True
for i in range(123):
a += 1 if b
The for-loop needs to stay as it is, since in my case there are other operations that affect b.
Since noone seems to be posting, why it goes like this, here is mine - lines:
a = a + 1 if b == True else a
a += 1 if b == True else a
are seen by python as:
a = (a + 1 if b == True else a)
a += (1 if b == True else a)
This is why you get large numbers fast in second version - you will add a to a, when b is False. If you want to keep the if, then go:
a += (1 if b else 0)
Also don't compare b to True (or False), go foif b`, as it's more pythonic (it will prevent some weird mistakes, when other code will start to interact with yours).
EDIT: go for #Tomerikoo answer for even shorter code, but keep in mind, that those waters can be muddy and not everyone knows / easily follows, that adding boolean to int treats first as 1 (or 0 if False).
To closest to your proal is probably:
a, b = 0, True
for i in range(123):
a += b
Since bool is a subtype of int, no conversion is necessary.
You can do:
for i in range(123):
if b:a+=1
You can also do:
for i in range(123):
a = a + 1*b
Because booleans are ints:
>>> isinstance(True, int)
True
>>> True == 1
True
just note that
a += x if condition else y
will resolve to a += x if the condition is True; otherwise it will be a += y. this is why your numbers get big...
apart from that i suggest you use what U10-Forward's answer suggests.

Simplifying an 'if' statement with bool()

I have some code that causes Pylint to complain:
The if statement can be replaced with 'var = bool(test)' (simplifiable-if-statement)`
The code (with obfuscated variable names) is below.
A = True
B = 1
C = [1]
D = False
E = False
if A and B in C:
D = True
else:
E = True
print(D, E)
How can this be simplified so that Pylint does not throw any errors?
I don't quite understand how bool() can be used for this. I know it converts any value to a Boolean value, but I don't know how it can be applied here.
That logic can be expressed as:
D = A and B in C
E = not D
Try this:
D = bool(A and B in C)
E = not bool(A and B in C)
I was initially a bit confused by the accepted answer and then realized my problem was not with the accepted answer itself (which is fully correct indeed) but with the specificity of the question. I needed a more generic / simple use case and so I will try to provide one, hoping it will be of help for someone.
The simplifiable-if-statement Pylint refactor basically happens when we use an if-else statement to assign a value to a boolean variable, which could have otherwise been assigned directly, without using the if-else statement at all.
A generic example could be:
if <condition>:
variable = True
else:
variable = False
which can (and should) be simplified as:
variable = <condition>
where <condition> is a boolean expression.
A concrete example:
if a > 5:
b = True
else:
b = False
should be rewritten as
b = a > 5
Getting back to the original question, in this case the condition is A and B in C and, as pointed out by other contributors, the redundant snippet:
D = False
E = False
if A and B in C:
D = True
else:
E = True
should be simplified as
D = A and B in C
E = not D

"A is not B" vs "A is (not B)"

I'm a little bit scared about the "is not" operator and the possibility that "is not X" is interpreted when "is (not X)" was intended. Do exist some expressions A and B such that:
A is not B
is different from
A is (not B)
?
addendum.
Is it considered good practice to use this operator? Should't not (A is B) be preferred?
They're definitely different. The latter case evaluates not X in a boolean context first and then checks to see if the two objects are the same object (either True or False).
Consider:
False is not []
This expression is trivially True since False and [] are quite clearly different objects. 1
vs.
False is (not [])
This expression is False since not [] evalutes to True and False and True are different objects.
Of course, this is just one example. It gets even easier to find examples if you don't use False and True explicitly as the second expression will always be False and the first expression will (almost) always be True...
3 is not 0 # True
3 is (not 0) # False
1Note that is not is a single operator in the same vein as not in.
Yes:
A = 0
B = 1
Try it and you'll be really scared:
>>> A = 0
>>> B = 1
>>> A is not B
True
>>> A is (not B)
False

Python check if there's any empty string in multiple strings

I know it's a basic question but please bear with me. Let's say if we have 4 strings below:
a = ''
b = 'apple'
c = 'orange'
d = 'banana'
So, normally if I want to check if any of the three string a b c is empty, I could use len() function.
if len(a) == 0 or len(b) == 0 or len(c) == 0:
return True
But then I thought it is too troublesome to write like above if I have many strings. So, I used
if not a:
return True
But, when i am checking for multiple strings b c d using the above method, it returns True and I am puzzled as non of the strings b c d where empty.
if not b or c or d:
return True
What is going on?
The problem lies with this line:
if not b or c or d:
You need to include the "not" condition for each string. So:
if not b or not c or not d:
You could also do it like this:
return '' in [a, b, c, d]
The not operator has higher precedence than or.
return not b or not c or not d
should work.

Categories

Resources