提高代码可读性和效率的 11 个技巧

1、受保护的 If

受保护的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.")
    # 更多处理逻辑在这里

2、提前退出

如果我们可以停止函数的进一步执行,我们将提高效率。一个常见的例子是在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'}

3、避免不必要的迭代

与提前退出类似,我们可以使用 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)

4、 使用否定条件

使用否定条件可以帮助避免“毁灭金字塔”,这种情况发生在你在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)

5、解释变量

如果某些值具有重要含义,例如众所周知的数字或传递给函数的标志,则可以通过引入解释变量来提高可读性。该变量的唯一目的是阐明该值的含义。

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

6、使用有意义的变量名

遵循可读性的主题,优先使用有意义的名称而不是简洁的名称。您的代码被读取的次数比编写的次数要多,通过使用描述性名称,您和其他人在重新阅读代码时可以更轻松。

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)

7、不要重复自己,但重复一点也没关系(DRY原则)

下一个技术有些争议,所以我将提供我和其他人遵循的一般规则:如果您看到代码块重复大约三次或更多,请考虑将其转换为函数。

但是,请注意不要过度遵循 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}")

8、使用布尔条件进行赋值

条件句不仅适用于 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  # 无需标记,直接使用条件表达式即可。

9、在局部声明变量

当您第一次学习编程时,您可能会养成将所有变量放在顶部的习惯。虽然这在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!")

10、使用缓存来避免昂贵的计算

使用此技术不仅可以避免不必要的执行,还可以避免昂贵的执行。初始化一个缓存或变量来保存计算结果或已被函数“看到”的输入和请求的资源可以极大地提高性能。

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

11、使用映射和字典来避免过度迭代

继续避免过度迭代的主题,有时,你可以通过改变算法使用的数据结构类型来提高效率。例如,通过使用映射或字典,您可以通过执行直接查找来完全避免迭代。

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'}