PEP 308: Conditional Expressions - The Ternary Operator
TL;DR
PEP 308 introduced conditional expressions in Python 2.5 with the syntax value_if_true if condition else value_if_false
, providing a concise way to assign values based on conditions.
Interesting!
Python’s conditional expression syntax was heavily debated - over 30 different syntaxes were proposed! The chosen syntax deliberately reads like English: “take this value if this condition is true, otherwise take that value.”
Basic Conditional Expression Syntax
python code snippet start
# Traditional if-else statement
temperature = 25
if temperature > 20:
clothing = "t-shirt"
else:
clothing = "jacket"
# Conditional expression (ternary operator)
clothing = "t-shirt" if temperature > 20 else "jacket"
# More examples
age = 17
status = "adult" if age >= 18 else "minor"
score = 85
grade = "pass" if score >= 60 else "fail"
# With function calls
name = ""
display_name = name if name else "Anonymous"
# Equivalent to
display_name = name or "Anonymous" # But conditional expression is more explicit
python code snippet end
Comparison with Traditional If-Else
python code snippet start
# Before PEP 308 - multiple approaches were used
# 1. Traditional if-else (verbose for simple cases)
x = 10
if x > 5:
result = "big"
else:
result = "small"
# 2. Using logical operators (less readable)
result = x > 5 and "big" or "small" # Dangerous if "big" is falsy!
# 3. Using a dictionary (overkill for simple cases)
result = {True: "big", False: "small"}[x > 5]
# PEP 308 solution (clear and concise)
result = "big" if x > 5 else "small"
python code snippet end
Practical Examples
Default Values
python code snippet start
# Setting default values
def greet(name=None):
display_name = name if name is not None else "Guest"
return f"Hello, {display_name}!"
print(greet("Alice")) # Hello, Alice!
print(greet()) # Hello, Guest!
# Configuration with defaults
config = {}
debug_mode = config.get('debug') if 'debug' in config else False
max_retries = config.get('retries') if config.get('retries') else 3
# API response handling
api_response = {"data": [1, 2, 3]}
data = api_response["data"] if "data" in api_response else []
python code snippet end
Mathematical Operations
python code snippet start
# Absolute value implementation
def my_abs(x):
return x if x >= 0 else -x
# Finding minimum/maximum
a, b = 10, 20
minimum = a if a < b else b
maximum = a if a > b else b
# Clipping values to range
def clip(value, min_val, max_val):
return min_val if value < min_val else max_val if value > max_val else value
print(clip(15, 10, 20)) # 15
print(clip(5, 10, 20)) # 10
print(clip(25, 10, 20)) # 20
python code snippet end
String Processing
python code snippet start
# Pluralization
def pluralize(word, count):
return word if count == 1 else word + "s"
print(f"You have {5} {pluralize('apple', 5)}") # You have 5 apples
print(f"You have {1} {pluralize('apple', 1)}") # You have 1 apple
# Safe string operations
text = " Python Programming "
cleaned = text.strip() if text else ""
# URL building
base_url = "https://api.example.com"
endpoint = "users"
use_ssl = True
url = f"https://{endpoint}" if use_ssl else f"http://{endpoint}"
# File extension handling
filename = "document.pdf"
extension = filename.split('.')[-1] if '.' in filename else "txt"
python code snippet end
Nested Conditional Expressions
python code snippet start
# Multiple conditions
score = 85
grade = "A" if score >= 90 else "B" if score >= 80 else "C" if score >= 70 else "F"
# More readable with parentheses
grade = (
"A" if score >= 90 else
"B" if score >= 80 else
"C" if score >= 70 else
"F"
)
# Complex nested example
age = 25
income = 50000
status = (
"eligible" if age >= 18 else "too_young"
) if income >= 30000 else (
"low_income" if age >= 18 else "young_and_low_income"
)
# Traffic light logic
light_color = "yellow"
action = (
"go" if light_color == "green" else
"slow_down" if light_color == "yellow" else
"stop"
)
python code snippet end
With List Comprehensions
python code snippet start
# Conditional expressions in list comprehensions
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Transform negative/positive
signed_numbers = [-2, -1, 0, 1, 2]
descriptions = [
"negative" if x < 0 else "zero" if x == 0 else "positive"
for x in signed_numbers
]
print(descriptions) # ['negative', 'negative', 'zero', 'positive', 'positive']
# Even/odd labeling
labels = ["even" if x % 2 == 0 else "odd" for x in numbers]
# Grade assignment
scores = [95, 87, 76, 64, 52]
grades = ["A" if s >= 90 else "B" if s >= 80 else "C" if s >= 70 else "D" if s >= 60 else "F" for s in scores]
# Data cleaning
raw_data = [" hello ", "", " world ", None]
cleaned_data = [
item.strip() if item and isinstance(item, str) else ""
for item in raw_data
]
python code snippet end
Function Arguments and Returns
python code snippet start
# Default function arguments
def calculate_tax(amount, rate=None):
tax_rate = rate if rate is not None else 0.1
return amount * tax_rate
# Function returns
def divide(a, b):
return a / b if b != 0 else float('inf')
def safe_get_first(lst):
return lst[0] if lst else None
# Method chaining safety
class Calculator:
def __init__(self):
self.value = 0
def add(self, x):
self.value += x
return self
def multiply(self, x):
self.value *= x
return self
def get_result(self):
return self.value
# Safe method calls
calc = Calculator()
result = calc.add(5).multiply(2).get_result() if calc else 0
python code snippet end
Real-World Use Cases
Configuration Management
python code snippet start
import os
class AppConfig:
def __init__(self):
# Environment-based configuration
self.debug = os.getenv('DEBUG', '').lower() == 'true'
self.database_url = (
os.getenv('DATABASE_URL') if os.getenv('DATABASE_URL')
else 'sqlite:///app.db'
)
self.max_connections = (
int(os.getenv('MAX_CONNECTIONS')) if os.getenv('MAX_CONNECTIONS')
else 10
)
# Feature flags
self.enable_cache = (
os.getenv('ENABLE_CACHE', 'true').lower() == 'true'
if 'ENABLE_CACHE' in os.environ
else True
)
config = AppConfig()
print(f"Debug mode: {config.debug}")
print(f"Database: {config.database_url}")
python code snippet end
API Response Handling
python code snippet start
def process_api_response(response):
"""Process API response with safe value extraction"""
# Safe data extraction
user_id = response.get('user_id') if response.get('user_id') else 'unknown'
# Status code handling
status = (
"success" if response.get('status_code') == 200
else "not_found" if response.get('status_code') == 404
else "error"
)
# Data transformation
users = response.get('data', [])
user_names = [
user.get('name') if user.get('name') else f"User_{user.get('id', 'unknown')}"
for user in users
]
return {
'user_id': user_id,
'status': status,
'user_names': user_names
}
# Mock API response
api_response = {
'status_code': 200,
'user_id': 123,
'data': [
{'id': 1, 'name': 'Alice'},
{'id': 2, 'name': ''},
{'id': 3}
]
}
result = process_api_response(api_response)
print(result)
python code snippet end
Form Validation
python code snippet start
def validate_user_input(data):
"""Validate user input with conditional expressions"""
errors = []
# Name validation
name = data.get('name', '').strip()
name_error = (
"Name is required" if not name
else "Name too short" if len(name) < 2
else "Name too long" if len(name) > 50
else None
)
if name_error:
errors.append(name_error)
# Email validation
email = data.get('email', '').strip()
email_error = (
"Email is required" if not email
else "Invalid email format" if '@' not in email
else None
)
if email_error:
errors.append(email_error)
# Age validation
age = data.get('age')
age_error = (
"Age is required" if age is None
else "Age must be positive" if age < 0
else "Age must be realistic" if age > 150
else None
)
if age_error:
errors.append(age_error)
return {
'valid': len(errors) == 0,
'errors': errors,
'sanitized_data': {
'name': name if name else None,
'email': email.lower() if email else None,
'age': age if isinstance(age, int) and age >= 0 else None
}
}
# Test validation
user_data = {'name': ' Alice ', 'email': 'ALICE@EXAMPLE.COM', 'age': 25}
validation_result = validate_user_input(user_data)
print(validation_result)
python code snippet end
Performance Considerations
python code snippet start
import time
# Conditional expressions are efficient
def benchmark_conditional_styles(n=1000000):
"""Compare performance of different conditional approaches"""
# Setup
values = list(range(n))
# Traditional if-else
start = time.time()
results1 = []
for x in values:
if x % 2 == 0:
results1.append("even")
else:
results1.append("odd")
time1 = time.time() - start
# Conditional expression
start = time.time()
results2 = ["even" if x % 2 == 0 else "odd" for x in values]
time2 = time.time() - start
# Using logical operators (old style)
start = time.time()
results3 = [x % 2 == 0 and "even" or "odd" for x in values]
time3 = time.time() - start
print(f"Traditional if-else: {time1:.3f}s")
print(f"Conditional expression: {time2:.3f}s")
print(f"Logical operators: {time3:.3f}s")
# Verify results are identical
assert results1 == results2 == results3
# benchmark_conditional_styles()
python code snippet end
Best Practices and Common Pitfalls
python code snippet start
# Good: Clear and readable
status = "active" if user.is_logged_in else "inactive"
# Good: With parentheses for complex conditions
message = (
"Welcome back!" if user.is_returning and user.has_premium
else "Welcome!" if user.is_returning
else "Please sign up"
)
# Avoid: Too many nested conditions (hard to read)
# Bad
result = "a" if x > 10 else "b" if x > 5 else "c" if x > 0 else "d"
# Better: Use traditional if-elif-else for complex logic
if x > 10:
result = "a"
elif x > 5:
result = "b"
elif x > 0:
result = "c"
else:
result = "d"
# Good: Short variable names for simple cases
sign = "+" if number >= 0 else "-"
# Good: Descriptive variable names for complex cases
user_access_level = (
"admin" if user.is_admin
else "moderator" if user.is_moderator
else "user"
)
# Avoid: Side effects in conditional expressions
# Bad (don't do this)
result = foo() if condition else bar() # Both functions might have side effects
# Good: Calculate first, then choose
foo_result = foo() if condition else None
bar_result = bar() if not condition else None
result = foo_result or bar_result
python code snippet end
Edge Cases and Gotchas
python code snippet start
# Be careful with truthiness
items = []
display = items if items else "No items" # Correct
# vs
display = items if items is not None else "No items" # Different behavior
# Zero, empty string, empty list are falsy
count = 0
message = f"Found {count} items" if count else "No items found"
print(message) # "No items found" - might not be intended
# Better: explicit comparison
message = f"Found {count} items" if count > 0 else "No items found"
# None vs falsy values
value = 0
result = value if value is not None else "default" # result is 0
result = value if value else "default" # result is "default"
# Mutable default arguments
def process_data(data=None):
items = data if data is not None else [] # Correct
# vs
items = data or [] # Works but less explicit about intent
return items
# Short-circuit evaluation doesn't apply
# Both sides are always evaluated in conditional expressions
def expensive_true():
print("Computing true value...")
return "expensive_true"
def expensive_false():
print("Computing false value...")
return "expensive_false"
# Both functions are called!
result = expensive_true() if True else expensive_false()
# Output: Computing true value...
# Computing false value...
# For short-circuit behavior, use logical operators
result = True and expensive_true() or expensive_false()
# Output: Computing true value...
python code snippet end
Conditional expressions provide a clean, readable way to assign values based on conditions, making code more concise while maintaining clarity.
Reference: PEP 308 - Conditional Expressions