受保护的if 模式是一种技术,其中特殊情况立即得到处理,从而使主逻辑可以不进行不必要的检查。
这种方法还有助于避免函数的进一步执行,从而引导我们找到下一个提示。
修改前
def process_customer_data(user):
if user is not None:
if user['customer']:
print("Processing customer data.")
# 更多处理逻辑在这里
else:
print("User is not a customer.")
return
# 边缘情况
else:
print("No user data provided.")
# 边缘情况
修改后
def process_customer_data(user):
if user is None: # 这是受保护的if,它“保护”着函数的其余部分免受边缘情况的影响。
print("No user data provided.")
# 边缘情况
return
if not user['customer']:
print("User is not a customer.")
# 边缘情况
return
print("Processing customer data.")
# 更多处理逻辑在这里
如果我们可以停止函数的进一步执行,我们将提高效率。一个常见的例子是在find函数中遍历列表。一旦找到所需的元素,我们应该立即从函数返回。无需迭代其余元素。
前
def find_user(users, username):
result = None
for user in users:
if user['username'] == username:
result = user
return result
users = [{'username': 'alice'}, {'username': 'bob'}]
print(find_user(users, 'bob')) # Output: {'username': 'bob'}
后
def find_user(users, username):
for user in users:
if user['username'] == username:
return user # 找到我们要找的东西后,就退出函数吧!
return None
users = [{'username': 'alice'}, {'username': 'bob'}]
print(find_user(users, 'bob')) # Output: {'username': 'bob'}
与提前退出类似,我们可以使用 break 来防止不必要的迭代,或者使用 continue 来停止当前迭代并继续下一次迭代。
前
def process_transactions(transactions, limit):
for transaction in transactions:
if transaction['status'] == 'invalid':
print(f"Skipping invalid transaction: {transaction['id']}")
else:
if transaction['amount'] > limit:
print(f"Alert! Transaction {transaction['id']} exceeds the limit.")
else:
print(f"Processing transaction: {transaction['id']} with amount {transaction['amount']}")
# 即使找到大额交易,也要继续检查
# Usage
transactions = [
{'id': 1, 'amount': 100, 'status': 'valid'},
{'id': 2, 'amount': 250, 'status': 'valid'},
{'id': 3, 'amount': 300, 'status': 'invalid'},
{'id': 4, 'amount': 500, 'status': 'valid'},
{'id': 5, 'amount': 150, 'status': 'valid'},
]
limit = 400
process_transactions(transactions, limit)
后
def process_transactions(transactions, limit):
for transaction in transactions:
if transaction['status'] == 'invalid':
print(f"Skipping invalid transaction: {transaction['id']}")
continue # 跳过处理这笔交易
if transaction['amount'] > limit:
print(f"Alert! Transaction {transaction['id']} exceeds the limit.")
break # 停止处理更多交易
# 处理限额内的有效交易
print(f"Processing transaction: {transaction['id']} with amount {transaction['amount']}")
# Usage
transactions = [
{'id': 1, 'amount': 100, 'status': 'valid'},
{'id': 2, 'amount': 250, 'status': 'valid'},
{'id': 3, 'amount': 300, 'status': 'invalid'},
{'id': 4, 'amount': 500, 'status': 'valid'},
{'id': 5, 'amount': 150, 'status': 'valid'},
]
limit = 400
process_transactions(transactions, limit)
使用否定条件可以帮助避免“毁灭金字塔”,这种情况发生在你在if语句的True或False(else)块中编写了大量逻辑。当你首先检查否定条件时,可以尽早捕捉边缘情况,保持“快乐路径”尽可能左对齐,并提高整体可读性。如果你开始看到许多else语句,考虑否定条件以简化代码
。
前
def process_files_in_directory(directory):
if os.path.exists(directory):
files = os.listdir(directory)
if len(files) > 0:
for file in files:
file_path = os.path.join(directory, file)
if os.path.isfile(file_path) and os.access(file_path, os.R_OK):
# This is what our functions is supposed to do
# it's indented very far from the left.
print(f"Processing file: {file}")
process_file(file)
else:
print(f"Cannot read file: {file}")
else:
print("No files to process in the directory.")
else:
print("Directory does not exist.")
后
def process_files_in_directory(directory):
if not os.path.exists(directory):
print("Directory does not exist.")
return
files = os.listdir(directory)
if not files:
print("No files to process in the directory.")
return
for file in files:
file_path = os.path.join(directory, file)
if not os.path.isfile(file_path) or not os.access(file_path, os.R_OK):
print(f"Cannot read file: {file}")
continue
# Using negated conditions helps us keep the "Happy Path" of our code
# as far left as possible
print(f"Processing file: {file}")
process_file(file)
如果某些值具有重要含义,例如众所周知的数字或传递给函数的标志,则可以通过引入解释变量来提高可读性。该变量的唯一目的是阐明该值的含义。
前
def convert_mb_to_gb(megabytes):
return megabytes / 1024
后
def convert_mb_to_gb(megabytes):
num_mb_in_gb = 1024
gigabytes = megabytes / num_mb_in_gb
return gigabytes
遵循可读性的主题,优先使用有意义的名称而不是简洁的名称。您的代码被读取的次数比编写的次数要多,通过使用描述性名称,您和其他人在重新阅读代码时可以更轻松。
前
for k, v in {"apple", 10, "pear": 5, "orange": 6}.items():
print(k, v)
后
for fruit, qty in {"apple", 10, "pear": 5, "orange": 6}.items():
print(fruit, qty)
下一个技术有些争议,所以我将提供我和其他人遵循的一般规则:如果您看到代码块重复大约三次或更多,请考虑将其转换为函数。
但是,请注意不要过度遵循 DRY(不要重复自己)原则。过度抽象和过度设计会让你的代码更难理解和维护,因为你很难记住算法的上下文。
保持代码的DRY原则不仅适用于函数,也适用于循环。如果您发现重复,请考虑使用循环来简化代码。
前
# 计算矩形1的面积和周长
length1 = 5
width1 = 3
area1 = length1 * width1
perimeter1 = 2 * (length1 + width1)
print(f"Rectangle 1 - Area: {area1}, Perimeter: {perimeter1}")
# 计算矩形2的面积和周长
length2 = 8
width2 = 6
area2 = length2 * width2
perimeter2 = 2 * (length2 + width2)
print(f"Rectangle 2 - Area: {area2}, Perimeter: {perimeter2}")
# 计算矩形3的面积和周长
length3 = 7
width3 = 4
area3 = length3 * width3
perimeter3 = 2 * (length3 + width3)
print(f"Rectangle 3 - Area: {area3}, Perimeter: {perimeter3}")
后
def calculate_area_and_perimeter(length, width):
area = length * width
perimeter = 2 * (length + width)
return area, perimeter
# 具有各自长度和宽度的矩形列表
rectangles = [(5, 3),(8, 6),(7, 4),]
# 循环遍历每个矩形并计算面积和周长
for i, (length, width) in enumerate(rectangles, start=1):
area, perimeter = calculate_area_and_perimeter(length, width)
print(f"Rectangle {i} - Area: {area}, Perimeter: {perimeter}")
条件句不仅适用于 if 语句;它们经常出现在 while 循环中,也可以单独用作布尔表达式。与其在 if 语句中使用标志变量,不如考虑直接使用布尔表达式。
前
def has_access(role, is_active):
access = False # 标志变量
if role == 'admin' and is_active:
access = True
return access
后
def has_access(role, is_active):
return role == 'admin' and is_active # 无需标记,直接使用条件表达式即可。
当您第一次学习编程时,您可能会养成将所有变量放在顶部的习惯。虽然这在C等语言中很好,但大多数较新语言的风格是在靠近使用变量的位置声明变量,也称为局部变量。这种方法提高了可读性并有助于保持代码的组织性。
前
# 在顶部声明的变量
min_number = 1
max_number = 100
target_number = random.randint(min_number, max_number)
guess = None
attempts = 0
max_attempts = 10
game_won = False
print(f"Welcome to the Number Guessing Game!")
print(f"Try to guess the number I'm thinking of between {min_number} and {max_number}.")
while attempts < max_attempts and not game_won:
try:
guess = int(input("Enter your guess: "))
attempts += 1
if guess < target_number:
print("Too low! Try again.")
elif guess > target_number:
print("Too high! Try again.")
else:
print(f"Congratulations! You've guessed the number {target_number} in {attempts} attempts.")
game_won = True
except ValueError:
print("Please enter a valid number.")
if not game_won:
print(f"Sorry, you've used all {max_attempts} attempts. The number was {target_number}. Better luck next time!")
后
import random
print("Welcome to the Number Guessing Game!")
# 这些将在接下来的两个语句中使用
min_number = 1
max_number = 100
print(f"Try to guess the number I'm thinking of between {min_number} and {max_number}.")
target_number = random.randint(min_number, max_number)
#These Belong to the Main Loop
attempts = 0
max_attempts = 10
game_won = False
while attempts < max_attempts and not game_won:
try:
# 只有在需要时才声明它。
guess = int(input("Enter your guess: "))
attempts += 1
if guess < target_number:
print("Too low! Try again.")
elif guess > target_number:
print("Too high! Try again.")
else:
print(f"Congratulations! You've guessed the number {target_number} in {attempts} attempts.")
game_won = True
except ValueError:
print("Please enter a valid number.")
if not game_won:
print(f"Sorry, you've used all {max_attempts} attempts. The number was {target_number}. Better luck next time!")
使用此技术不仅可以避免不必要的执行,还可以避免昂贵的执行。初始化一个缓存或变量来保存计算结果或已被函数“看到”的输入和请求的资源可以极大地提高性能。
前
def is_prime(n):
# 处理数字小于或等于 1 的边缘情况
if n <= 1:
return False
# 检查不超过 n 的平方根的潜在因数的可除性
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
return False
# 如果找不到被除数,则该数为质数
return True
# 检查数字是否为质数
print(is_prime(29)) # True
print(is_prime(15)) # False
print(is_prime(29)) # True (Recalculated)
后
# 定义全局缓存字典
prime_cache = {}
def is_prime(n):
# 检查结果是否已缓存,避免计算
if n in prime_cache:
return prime_cache[n]
# 处理小于或等于 1 的数字的边缘情况
if n <= 1:
prime_cache[n] = False
return False
# 检查不超过 n 的平方根的潜在因数的可除性
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
prime_cache[n] = False
return False
# 如果没有找到约数,则该数字是质数
prime_cache[n] = True
return True
# 使用缓存测试 is_prime 函数
print(is_prime(29)) # True, calculated and stored
print(is_prime(15)) # False, calculated and stored
print(is_prime(29)) # True, retrieved from cache
print(is_prime(15)) # False, retrieved from cache
继续避免过度迭代的主题,有时,你可以通过改变算法使用的数据结构类型来提高效率。例如,通过使用映射或字典,您可以通过执行直接查找来完全避免迭代。
前
students_list = [
{"id": 101, "name": "Alice", "grade": "A"},
{"id": 102, "name": "Bob", "grade": "B"},
{"id": 103, "name": "Charlie", "grade": "C"},
]
def find_student_by_id_list(student_id, students):
for student in students:
if student["id"] == student_id:
return student
return None
# Find student with ID 102
student = find_student_by_id_list(102, students_list)
print(student) # Output: {'id': 102, 'name': 'Bob', 'grade': 'B'}
后
students_dict = {
101: {"name": "Alice", "grade": "A"},
102: {"name": "Bob", "grade": "B"},
103: {"name": "Charlie", "grade": "C"},
}
def find_student_by_id_dict(student_id, students):
return students.get(student_id)
# Find student with ID 102
student = find_student_by_id_dict(102, students_dict)
print(student) # Output: {'name': 'Bob', 'grade': 'B'}