multiple assignments with a comma in python - python

I tried to find an explanation of this, the Gotcha part:
b = "1984"
a = b, c = "AB"
print(a, b, c)
returns:
('AB', 'A', 'B')
I understand what happens with multiple equals:
a = b = 1
but using it together with a comma, I cannot understand the behaviour, ideas in why it works that way?

The answer is
a = b, c ="AB"
acts like:
a = (b, c) = "AB"
This is why:
a = "AB" and b = "A" and c = "B"

a = b, c = "AB"
Is not interpreted the way you think it does. You do have a multiple assignment, but it is not a = b and c = "AB". It is c,b = "AB" and a = "AB". In python
x = y = z = 1
Is interpreted as x, y, and z getting assigned value 1. And comma is used to unpack lists of values into individual variables, so b, c = "AB" unpacks "AB" into "A" and "B". So at the end of this line,
a = b, c = "AB"
a == "AB"
b == "A"
c == "B"

This comes down to order of operations, line 2 is actually two different statements.
a = b
is completed first. Then
b, c = "AB"
which is unpacking the value of "AB" and assigning "A" to b and "B" to c.

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

Use cases for "and" in variable assignment

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.

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

Safely unpacking results of str.split [duplicate]

This question already has answers here:
How do I reliably split a string in Python, when it may not contain the pattern, or all n elements?
(5 answers)
Closed 5 years ago.
I've often been frustrated by the lack of flexibility in Python's iterable unpacking. Take the following example:
a, b = "This is a string".split(" ", 1)
Works fine. a contains "This" and b contains "is a string", just as expected. Now let's try this:
a, b = "Thisisastring".split(" ", 1)
Now, we get a ValueError:
ValueError: not enough values to unpack (expected 2, got 1)
Not ideal, when the desired result was "Thisisastring" in a, and None or, better yet, "" in b.
There are a number of hacks to get around this. The most elegant I've seen is this:
a, *b = mystr.split(" ", 1)
b = b[0] if b else ""
Not pretty, and very confusing to Python newcomers.
So what's the most Pythonic way to do this? Store the return value in a variable and use an if block? The *varname hack? Something else?
This looks perfect for str.partition:
>>> a, _, b = "This is a string".partition(" ")
>>> a
'This'
>>> b
'is a string'
>>> a, _, b = "Thisisastring".partition(" ")
>>> a
'Thisisastring'
>>> b
''
>>>
How about adding the default(s) at the end and throwing away the unused ones?
>>> a, b, *_ = "This is a string".split(" ", 1) + ['']
>>> a, b
('This', 'is a string')
>>> a, b, *_ = "Thisisastring".split(" ", 1) + ['']
>>> a, b
('Thisisastring', '')
>>> a, b, c, *_ = "Thisisastring".split(" ", 2) + [''] * 2
>>> a, b, c
('Thisisastring', '', '')
Similar (works in Python 2 as well):
>>> a, b, c = ("Thisisastring".split(" ", 2) + [''] * 2)[:3]
>>> a, b, c
('Thisisastring', '', '')
The *varname hack seems very pythonic to me:
Similar to how function parameters are handled
Lets you use a one-liner or if block or nothing to correct type of the element if desired
You could also try something like the following if you don't find that clear enough for new users
def default(default, tuple_value):
return tuple(map(lambda x: x if x is not None else default, tuple_value))
Then you can do something like
a, *b = default("", s.split(...))
Then you should be able to depend on b[0] being a string.
I fully admit that the definition of default is obscure, but if you like the effect, you can refine until it meets your aesthetic. In general this is all about what feels right for your style.

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