Shorter, more pythonic way of writing an if statement - python

I have this
bc = 'off'
if c.page == 'blog':
bc = 'on'
print(bc)
Is there a more pythonic (and/or shorter) way of writing this in Python?

Shortest one should be:
bc = 'on' if c.page=='blog' else 'off'
Generally this might look a bit confusing, so you should only use it when it is clear what it means. Don't use it for big boolean clauses, since it begins to look ugly fast.

This is:
definitely shorter
arguably Pythonic (pre-Python 2.5, which introduced the controversial X if Z else Y syntax)
questionably readable. With those caveats in mind, here it goes:
bc = ("off","on")[c.page=="blog"]
EDIT: As per request, the generalized form is:
result = (on_false, on_true)[condition]
Explanation: condition can be anything that evaluates to a Boolean. It is then treated as an integer since it is used to index the tuple: False == 0, True == 1, which then selects the right item from the tuple.

Well, not being a python guy please take this with a huge grain of salt, but having written (and, with more difficulty, read) a lot of clever code over the years, I find myself with a strong preference now for readable code. I got the gist of what your original code was doing even though I'm a nobody as a Python guy. To be sure, you could hide it and maybe impress a Python wonk or two, but why?

You could use an inline if statement:
>>> cpage = 'blog'
>>> bc = 'on' if cpage == 'blog' else 'off'
>>> bc
'on'
>>> cpage = 'asdf'
>>> bc = 'on' if cpage == 'blog' else 'off'
>>> bc
'off'
There's a bit of a writeup on that feature at this blog, and the relevant PEP is PEP308. The inline if statement was introduced in Python 2.5.
This one is less pythonic, but you can use and/or in this fashion:
>>> cpage = 'asdf'
>>> bc = (cpage == 'blog') and 'on' or 'off'
>>> bc
'off'
>>> cpage = 'blog'
>>> bc = (cpage == 'blog') and 'on' or 'off'
>>> bc
'on'
This one is used more often in lambda statements than on a line by itself, but the form
A and B or C
is similar to
if A:
return B
else:
return C
The major caveat to this method (as PEP 308 mentions) is that it returns C when B is false.

Another possibility is to use a dict if you can compute the values outside of the function that accesses them (i.e. the values are static, which also addresses the evaluation issue in scrible's answer's comments).
want_bc = {True: "on", False: "off"}
# ...
bc = want_bc[c.page == "blog"]
I prefer this and/or the tuple indexing solutions under the general rubric of preferring computation to testing.

You can use,
a = b if c else d
but if you are using a python version prior to 2.5,
bc = c.page == "blog" and "on" or "off"
can do the trick also.

Related

Comparing "float('nan')" and "math.nan"

I have a float variable which may or may not be a number, and I want to check if that is the case. With x = float('nan'), I observed some behavior that surprised me:
print(x is math.nan)
>>> False
This means that float('nan') and math.nan are different objects, which I didn't expect, but that's okay. However, the result is the same, when I check for equality with ==:
print(x == math.nan):
>>> False
I get the correct result for all kinds of not-a-number, if I use math.isnan(x). Still, why doesn't float('nan') == math.nan evaluate to True?.
"Not a number" is (in some sense) the absence of a value.
Traditionally, and per the IEEE floating-point specification, it does not equal itself.
That's because there is no meaningful value to compare.
In fact, some people use this fact to detect NaN, so you could try x != x as your condition instead (though the linked Q&A arguably has some better suggestions).
The expression math.nan is math.nan is true, though, because is does an object identity comparison rather than a value equivalence/equality comparison.
This is not special behaviour: is returns whether two object are actually referring to the same thing (essentially in memory) and == returns whether two objects have the same value.
To see if they refer to the same thing, we can use id().
>>> a = [1,2,3]
>>> b = a
>>> id(a)
140302781856200
>>> id(b)
140302781856200
>>> a == b
True
>>> a is b
True
>>> c = [1,2,3]
>>> id(c)
140302781864904
>>> a == c
True
>>> a is c
False
Here we see that by assigning b = a, they now refer to the same list: hence is and == are True. However when we define c to be a new variable with the same value as a and b, it is ==, but is returns False.
The same is true for NaNs.
That is because NaN is just a float value. Using is doesn't check for whether the variables have the same value, it checks whether they are the same object. If you create two floats with the same value, they are not the same object, they are two objects with the same value. Take this for example:
>>> a = float('nan')
>>> b = float('nan')
>>> a is b
False
So even if you create two NaN values the same way, they are not the same object. This is true even for more trivial floats. Try this:
>>> a = 1.
>>> b = 1.
>>> a is b
False
The default version of Python re-uses some values, so that any instance of that value is the same object. So take this for example (note the lack of decimal, these are integers not floats):
>>> a = 1
>>> b = 1
>>> a is b
True
But that is an implementation detail you should never rely on, it can change at any time and can vary between python implementations. But even with that, NaN is not one of the values the default Python interpreter does this for.
You can check whether two variables are the same object manually using the id function, which gives a unique number for each simultaneously-existing object (although the numbers can be re-used if a variable is deleted, even automatically).
>>> a=1.
>>> b=1.
>>> c=float('nan')
>>> d=float('nan')
>>> e=1
>>> f=1
>>> id(a)
139622774035752
>>> id(b)
139622774035872
>>> id(c)
139622774035824
>>> id(d)
139622774035800
>>> id(e)
139622781650528
>>> id(f)
139622781650528
As for why they aren't equal, that is just part of the definition of NaN as it is used on modern computers. By definition, NaN must never be equal to itself. It is part of an international standard on how floating-point numbers work, and this behavior is built into modern CPUs.
While they are not the same object (because they are from different modules where they were implemented separately) and they are not equal (by design NaN != NaN), there is the function math.isnan (and numpy.isnan if you want a vectorized version) exactly for this purpose:
import math
import numpy
math.isnan(math.nan)
# True
math.isnan(numpy.nan)
# True
math.isnan(float("nan"))
# True
Although they are all unequal to each other and not identical:
math.nan == numpy.nan or math.nan is numpy.nan
# False
math.nan == float("nan") or math.nan is float("nan")
# False
numpy.nan == float("nan") or numpy.nan is float("nan")
# False
You can use the "hex" function that is built into "float"
float('nan') == math.nan # FALSE
float('nan').hex() == math.nan.hex() # TRUE
float('nan').hex() == float('nan').hex() # TRUE
float('nan').hex() == numpy.nan.hex() # TRUE
This is very helpful if you are using queries in pandas. I recently was trying to use:
df.eval('A == "NaN"')
Which should check if column A is NaN. But, pandas was automatically converting the string, "NaN", into a float. Most people would recommend using df['A'].isna(), but in our case, trying to pass an expression into a method, so it should handle any expression.
The solution was to do:
df.applymap(lambda x: 'NaN' if x.hex() == float('NaN').hex() else x).eval('A == "NaN"')
You can convert the nan value to string for comparing.
somthing like this:
x=float("nan")
s_nan = str(x)
if s_nan == "nan":
# What you need to do...
print('x is not a number')

Compact way of writing (a + b == c or a + c == b or b + c == a)

Is there a more compact or pythonic way to write the boolean expression
a + b == c or a + c == b or b + c == a
I came up with
a + b + c in (2*a, 2*b, 2*c)
but that is a little strange.
If we look at the Zen of Python, emphasis mine:
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than right now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
The most Pythonic solution is the one that is clearest, simplest, and easiest to explain:
a + b == c or a + c == b or b + c == a
Even better, you don't even need to know Python to understand this code! It's that easy. This is, without reservation, the best solution. Anything else is intellectual masturbation.
Furthermore, this is likely the best performing solution as well, as it is the only one out of all the proposals that short circuits. If a + b == c, only a single addition and comparison is done.
Solving the three equalities for a:
a in (b+c, b-c, c-b)
Python has an any function that does an or on all the elements of a sequence. Here I've converted your statement into a 3-element tuple.
any((a + b == c, a + c == b, b + c == a))
Note that or is short circuiting, so if calculating the individual conditions is expensive it might be better to keep your original construct.
If you know you're only dealing with positive numbers, this will work, and is pretty clean:
a, b, c = sorted((a, b, c))
if a + b == c:
do_stuff()
As I said, this only works for positive numbers; but if you know they're going to be positive, this is a very readable solution IMO, even directly in the code as opposed to in a function.
You could do this, which might do a bit of repeated computation; but you didn't specify performance as your goal:
from itertools import permutations
if any(x + y == z for x, y, z in permutations((a, b, c), 3)):
do_stuff()
Or without permutations() and the possibility of repeated computations:
if any(x + y == z for x, y, z in [(a, b, c), (a, c, b), (b, c, a)]:
do_stuff()
I would probably put this, or any other solution, into a function. Then you can just cleanly call the function in your code.
Personally, unless I needed more flexibility from the code, I would just use the first method in your question. It's simple and efficient. I still might put it into a function:
def two_add_to_third(a, b, c):
return a + b == c or a + c == b or b + c == a
if two_add_to_third(a, b, c):
do_stuff()
That's pretty Pythonic, and it's quite possibly the most efficient way to do it (the extra function call aside); although you shouldn't worry too much about performance anyway, unless it's actually causing an issue.
If you will only be using three variables then your initial method:
a + b == c or a + c == b or b + c == a
Is already very pythonic.
If you plan on using more variables then your method of reasoning with:
a + b + c in (2*a, 2*b, 2*c)
Is very smart but lets think about why. Why does this work?
Well through some simple arithmetic we see that:
a + b = c
c = c
a + b + c == c + c == 2*c
a + b + c == 2*c
And this will have to hold true for either a,b, or c, meaning that yes it will equal 2*a, 2*b, or 2*c. This will be true for any number of variables.
So a good way to write this quickly would be to simply have a list of your variables and check their sum against a list of the doubled values.
values = [a,b,c,d,e,...]
any(sum(values) in [2*x for x in values])
This way, to add more variables into the equation all you have to do is edit your values list by 'n' new variables, not write 'n' equations
The following code can be used to iteratively compare each element with the sum of the others, which is computed from sum of the whole list, excluding that element.
l = [a,b,c]
any(sum(l)-e == e for e in l)
Don't try and simplify it. Instead, name what you're doing with a function:
def any_two_sum_to_third(a, b, c):
return a + b == c or a + c == b or b + c == a
if any_two_sum_to_third(foo, bar, baz):
...
Replace the condition with something "clever" might make it shorter, but it won't make it more readable. Leaving it how it is isn't very readable either however, because it's tricky to know why you're checking those three conditions at a glance. This makes it absolutely crystal clear what you're checking for.
Regarding performance, this approach does add the overhead of a function call, but never sacrifice readability for performance unless you've found a bottleneck you absolutely must fix. And always measure, as some clever implementations are capable of optimizing away and inlining some function calls in some circumstances.
Python 3:
(a+b+c)/2 in (a,b,c)
(a+b+c+d)/2 in (a,b,c,d)
...
It scales to any number of variables:
arr = [a,b,c,d,...]
sum(arr)/2 in arr
However, in general I agree that unless you have more than three variables, the original version is more readable.
(a+b-c)*(a+c-b)*(b+c-a) == 0
If the sum of any two terms is equal to the third term, then one of the factors will be zero, making the entire product zero.
How about just:
a == b + c or abs(a) == abs(b - c)
Note that this won't work if variables are unsigned.
From the viewpoint of code optimization (at least on x86 platform) this seems to be the most efficient solution.
Modern compilers will inline both abs() function calls and avoid sign testing and subsequent conditional branch by using a clever sequence of CDQ, XOR, and SUB instructions. The above high-level code will thus be represented with only low-latency, high-throughput ALU instructions and just two conditionals.
The solution provided by Alex Varga "a in (b+c, b-c, c-b)" is compact and mathematically beautiful, but I wouldn't actually write code that way because the next developer coming along would not immediately understand the purpose of the code.
Mark Ransom's solution of
any((a + b == c, a + c == b, b + c == a))
is more clear but not much more succinct than
a + b == c or a + c == b or b + c == a
When writing code that someone else will have to look at, or that I will have to look at a long time later when I have forgotten what I was thinking when I wrote it, being too short or clever tends to do more harm than good. Code should be readable. So succinct is good, but not so succinct that the next programmer can't understand it.
Request is for more compact OR more pythonic - I tried my hand at more compact.
given
import functools, itertools
f = functools.partial(itertools.permutations, r = 3)
def g(x,y,z):
return x + y == z
This is 2 characters less than the original
any(g(*args) for args in f((a,b,c)))
test with:
assert any(g(*args) for args in f((a,b,c))) == (a + b == c or a + c == b or b + c == a)
additionally, given:
h = functools.partial(itertools.starmap, g)
This is equivalent
any(h(f((a,b,c))))
I want to present what I see as the most pythonic answer:
def one_number_is_the_sum_of_the_others(a, b, c):
return any((a == b + c, b == a + c, c == a + b))
The general case, non-optimized:
def one_number_is_the_sum_of_the_others(numbers):
for idx in range(len(numbers)):
remaining_numbers = numbers[:]
sum_candidate = remaining_numbers.pop(idx)
if sum_candidate == sum(remaining_numbers):
return True
return False
In terms of the Zen of Python I think the emphasized statements are more followed than from other answer:
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than right now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
As an old habit of my programming, I think placing complex expression at right in a clause can make it more readable like this:
a == b+c or b == a+c or c == a+b
Plus ():
((a == b+c) or (b == a+c) or (c == a+b))
And also I think using multi-lines can also make more senses like this:
((a == b+c) or
(b == a+c) or
(c == a+b))
In a generic way,
m = a+b-c;
if (m == 0 || m == 2*a || m == 2*b) do_stuff ();
if, manipulating an input variable is OK for you,
c = a+b-c;
if (c==0 || c == 2*a || c == 2*b) do_stuff ();
if you want to exploit using bit hacks, you can use "!", ">> 1" and "<< 1"
I avoided division though it enables use to avoid two multiplications to avoid round off errors. However, check for overflows
def any_sum_of_others (*nums):
num_elements = len(nums)
for i in range(num_elements):
discriminating_map = map(lambda j: -1 if j == i else 1, range(num_elements))
if sum(n * u for n, u in zip(nums, discriminating_map)) == 0:
return True
return False
print(any_sum_of_others(0, 0, 0)) # True
print(any_sum_of_others(1, 2, 3)) # True
print(any_sum_of_others(7, 12, 5)) # True
print(any_sum_of_others(4, 2, 2)) # True
print(any_sum_of_others(1, -1, 0)) # True
print(any_sum_of_others(9, 8, -4)) # False
print(any_sum_of_others(4, 3, 2)) # False
print(any_sum_of_others(1, 1, 1, 1, 4)) # True
print(any_sum_of_others(0)) # True
print(any_sum_of_others(1)) # False
There is little to gain with such a small expression but using a function just to not having to repeat the summation and comparison could be an option. It makes it a bit more maintainable when wanting to change the operation to something like a + b == c * 2.
def equals_sum(a, b, c):
return a + b == c
if (equals_sum(a, b, c)
or equals_sum(a, c, b)
or equals_sum(b, c, a)):
...

Best way to do ternary conditionals in Python < 2.5

I have to bear with a Python version < 2.5 (it is 2.4.3 for specifics)
It seems that ternary operators were introduces in Python starting at 2.5. For those who are not familiar, ternary operators in Python >= 2.5 look like this:
def do_ternary(flag):
return "foo" if flag else "bar"
I'd like to know some solutions to emulate this in the early versions of Python. I can for sure do it with if ... else, but I'm looking for something more pythonic that I wouldn't be ashamed to put on some production-level code :)
Thanks for the help !
the correct way that does all of the things that if/else does is:
(condition and (yes_value,) or (no_value,))[0]
which does both the short circuiting and resolves the problem of when yes_value is itself falsey. Obviously, if you have reason to avoid this cludge, just do that; in your example, both conditions are constant expressions, so you can do:
{True: yes_value, False: no_value}[bool(condition)]
or more tersely:
(no_value, yes_value)[condition]
if you do need the short circut, but you're confident that the yes_value is never falsey, you can trim out the tuple:
condition and yes_value or no_value
but that's probably only valid when the yes_value is actually a constant. If none of these suit your tastes or needs, just use a plain-ol if: statement, with an intermediate variable
if condition:
result = yes_value
else:
result = no_value
Actually I was looking on the web and found what seems like a really elegant pythonic solution:
def _if(test):
return lambda alternative: \
lambda result: \
[delay(result), delay(alternative)][not not test]()
def delay(f):
if callable(f): return f
else: return lambda: f
>>> fact = lambda n: _if (n <= 1) (1) (lambda: n * fact(n-1))
>>> fact(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L
What do you think about this one? It looks pretty clean and easy to read in my opinion.
A common trick is to use list-indexing, since False/True turn into 0/1 when an integer is required. In case the test may be false-y or truth-y rather than a boolean, its good practice to first ensure the test is a boolean:
["bar", "foo"][bool(flag)]
will produce the same output as the ternary in your question.
Edit: Dougal points out that this may behave slightly differently from the ternary, because both the true and false values will be evaluated, which may have side-effects.
The classic 'trick' used to do this is:
test and true_value or false_value
This works as and and or work like so in python:
x or y -> if x is false, then y, else x
x and y -> if x is false, then x, else y
Source
This means that we get the roughly the same result - so long as true_value evaluates to True - so, for example, the following would not work:
flag and [] or "bar"
As [] evaluates to False.
I'd still argue that this is less readable than simply using an if/else block, as unless you are familiar with it, it's unclear.
So I'd advise using:
if test:
return true_value
else:
return false_value
(replacing return with assignment or whatever where needed).

Strange Python behavior from inappropriate usage of 'is not' comparison?

I (incorrectly?) used 'is not' in a comparison and found this curious behavior:
>>> a = 256
>>> b = int('256')
>>> c = 300
>>> d = int('300')
>>>
>>> a is not b
False
>>> c is not d
True
Obviously I should have used:
>>> a != b
False
>>> c != d
False
But it worked for a long time due to small-valued test-cases until I happened to
use a number of 495.
If this is invalid syntax, then why? And shouldn't I at least get a warning?
"is" is not a check of equality of value, but a check that two variables point to the same instance of an object.
ints and strings are confusing for this as is and == can happen to give the same result due to how the internals of the language work.
For small numbers, Python is reusing the object instances, but for larger numbers, it creates new instances for them.
See this:
>>> a=256
>>> b=int('256')
>>> c=300
>>> d=int('300')
>>> id(a)
158013588
>>> id(b)
158013588
>>> id(c)
158151472
>>> id(d)
158151436
which is exactly why a is b, but c isn't d.
Don't use is [not] to compare integers; use == and != instead. Even though is works in current CPython for small numbers due to an optimization, it's unreliable and semantically wrong. The syntax itself is valid, but the benefits of a warning (which would have to be checked on every use of is and could be problematic with subclasses of int) are presumably not worth the trouble.
This is covered elsewhere on SO, but I didn't find it just now.
Int is an object in python, and python caches small integer between [-5,256] by default, so where you use int in [-5,256], they are identical.
a = 256
b = 256
a is b # True
If you declare two integers not in [-5,256], python will create two objects which are not the same(though they have the same value).
a = 257
b = 257
a is b # False
In your case, using != instead to compare the value is the right way.
a = 257
b = 257
a != b # False
For more understanding why this occurs take a look to Python-2.6.5/Objects/intobject.c:78:small_ints array and Python-2.6.5/Objects/intobject.c:1292:_PyInt_Init function in python sources.
Also similar thing occurs with lists:
>>> a = [12]
>>> id_a = id(a)
>>> del(a)
>>> id([1,2,34]) == id_a
True
>>>
Removed lists are not destroyed. They are reused

How does the Python conditional operator workaround work?

From what I have read, I found that a built-in ternary operator does not exist (I will be happy to know more about it.).
I found the following code as a substitute:
def val():
var = float(raw_input("Age:"))
status = ("Working","Retired")[var>65]
print "You should be:",status
I couldn't understand how this code works; can anyone explain me how actually the code is working? I am also interested to know why the ternary operator doesn't exist; any references or links about this will be ore useful.
I'm running Python 2.6.4 on Windows Vista.
Python has a construct that is sort of like the ternary operator in C, et al. It works something like this:
my_var = "Retired" if age > 65 else "Working"
and is equivalent to this C code:
my_var = age > 65 ? "Retired" : "Working";
As for how the code you posted works, let's step through it:
("Working","Retired")
creates a 2-tuple (an immutable list) with the element "Working" at index 0, and "Retired" at index 1.
var>65
returns True if var is greater than 65, False if not. When applied to an index, it is converted into 1 (True) or 0 (False). Thus, this boolean value provides an index into the tuple created on the same line.
Why hasn't Python always had a ternary operator? The simple answer is that Guido van Rossum, the author of Python, didn't like/didn't want it, apparently believing that it was an unnecessary construct that could lead to confusing code (and anyone who's seen massively-nested ternary operators in C can probably agree). But for Python 2.5, he relented and added the grammar seen above.
Python (2.5 and above) does indeed have a syntax for what you are looking for:
x = foo if condition else bar
If condition is True, x will be set to foo, otherwise it will be set to bar.
Examples:
>>> age = 68
>>> x = 'Retired' if age > 65 else 'Working'
>>> x
'Retired'
>>> age = 35
>>> y = 'Retired' if age > 65 else 'Working'
>>> y
'Working'
because True casts to 1 and False casts to 0 so if var = 70
("Working","Retired")[var>65]
becomes
("Working", "Retired")[1]
a nice little shortcut ... but I find it can be a little confusing with anything but a simple condition, so I would go with TM's suggestion
"Retired" if var > 65 else "Working"
indexing into a list
The use of
[expression_when_false, expression_when_true][condition] # or
(expression_when_false, expression_when_true)[condition]
takes advantage of the fact that in Python True equals (but isn't!) 1 and False equals (but isn't!) 0. The expression above constructs a list of two elements, and uses the result of condition to index in the list and return only one expression. The drawback of this method is that both expressions are evaluated.
and-or shortcuts
Since the creation of Python, there was a form of this operation:
condition and expression_when_true or expression_when_false
This takes a shortcut and evaluates only one expression, but has a bug-prone drawback: the expression_when_true must not evaluate to a non-true value, otherwise the result is expression_when_false. and and or are "short-circuiting" in Python, and the following rules apply:
a and b #→ a if a is false, else b
a or b #→ a if a is true, else b
If condition is false, then expression_when_true is never evaluated and the result is expression_when_false. OTOH, if condition is true, then the result is the result of (expression_when_true or expression_when_false); consult the table above.
ternary conditional operator
Of course, since Python 2.5, there is a ternary conditional operator:
expression_when_true if condition else expression_when_false
The strange (if you are accustomed to the C-like ternary conditional operator) order of the operands is attributed to many things; the general intention is that condition should be true most of the time, so that the most common output comes first and is most visible.
Short-circuit boolean expressions
There is also an option to short-circuit logical operations:
>>> (2+2 == 4) and "Yes" or "No"
'Yes'
>>> (2+2 == 5) and "Yes" or "No"
'No'
In your example:
>>> (int(raw_input("Age: ")) > 65) and "Retired" or "Working"
Age: 20
'Working'
>>> (int(raw_input("Age: ")) > 65) and "Retired" or "Working"
Age: 70
'Retired'
Read more about this technique in Charming Python: Functional Programming in Python, Part 1.
in the code that you posted the following line is emulating ternary:
status = ("Working","Retired")[var>65]
here tuple ("Working","Retired") accessed with an index [var>65] which evaluates to either True (1) or False (0). When it's accessed with index 0, status will be 'Working'; if index is 1 then it'll be ``Retired'`. It's a fairly obscure way to do conditional assignment, use the normal ternary syntax that was introduced in py2.5 as was said.
There was originally no ternary operator because "Explicit is better than implicit", and it was seen as unpythonic. I don't like python's ternary op too much, either, but it exists:
x = foo if condition else bar
as shown by TM.
As for status = ("Working","Retired")[var>65],
var > 65 returns a boolean value: either True or False; however, Python treats boolean types quite weakly: True is 1 and False is 0 in some contexts. You can check it out by doing >>> True == 1.
status = ("Working","Retired")[var>65]
This line works as a ternary operator because the expression var>65 returns 1 or 0, depending on whether var is bigger than 65 or not. So if var>65, then the line becomes this:
status = ("Working","Retired")[1]
that is, the second element of the sequence ("Working","Retired"). It looks odd but not if you write it like this instead:
status_sequence = ("Working","Retired")
status = status_sequence[1]
so status = "Retired".
Similarly, if var<=65 then it becomes
status = ("Working","Retired")[0]
and status = "Working".
Only the "status =" line of that code implements something like the ternary operator.
status = ("Working","Retired")[var>65]
This creates a two-element tuple, with strings 'Working' at index 0, and 'Retired' at index 1. Following this, it indexes into that tuple to pick one of the two items, using the results of the expression var > 65.
This expression will return True (equivalent to 1, thus picking 'Retired') if the value of var is greater than 65. Otherwise it will return False (equivalent to 0, thus picking 'Working').
There is a key difference between this approach and the ternary operator, however, although it doesn't matter in your particular example. With the tuple-indexing approach, both values are evaluated but only one is returned. With the ternary operator, only one of the two values is actually evaluated; this is referred to as "short-circuit" behaviour. It can matter in cases like this:
status = funcA() if var > 65 else funcB()
status = (funcB(), funcA())[var > 65]
In the first case, either funcA() is called or funcB() is called, but never both. In the latter case, both are called first, and the results are stored in the tuple -- then only one is picked and the tuple is discarded.
This is especially important to understand if either funcA() or funcB() have "side-effects", meaning they change other data as they execute.
In Python 2.6 and up:
print "You should be {0}.".format("retired" if var>65 else "working")
In Python 3.1 and up:
print ("You should be {}.".format("retired" if var>65 else "working"))
this is the form with the python ternary operator
def val():
var = float(raw_input("Age:"))
status = "Retired" if var > 65 else "Working"
print "You should be:",status
the code you showed is a bit tricky: it creates a two elements tuple whose elements are at position 0 and 1. to select the right element it uses a condition which return a boolean but booleans in python are integers so you can use it as special indexes (they can be either 0 or 1).
trying to give a complete answer based on the answers given here.
the way you found (please don't use this one because it is not very readable):
def val():
var = float(raw_input("Age:"))
status = ("Working","Retired")[var>65]
print "You should be:",status
using the python 2.5+ syntax:
def val():
var = float(raw_input("Age:"))
status = "Working" if var>65 else "Retired"
print "You should be:",status
using the other common method still preferred by some people:
def val():
var = float(raw_input("Age:"))
status = var>65 and "Working" or "Retired"
print "You should be:",status
i personally tend to use the last since the order of the operands is the same as the C ternary operator.
EDIT:
found some problems with the last approach (thx Roberto Bonvallet).
from wikipedia:
this code would break if op1 could be
a "falsy" value (None, False, 0, an
empty sequence or collection, …) as
the expression would return op2
(whether it was truthy or falsy)
instead of the (falsy) op1
so my final suggestion would be to use the 2.5+ ternary operator since it is simple, readable and offers short-circuit behavior.

Categories

Resources