编写高效Python函数的五个技巧,真心建议你牢记,并当作此后编码的准绳!

开发 前端
在Python中使用生成器返回列表可以在内存效率、性能和惰性求值方面带来显著优势。通过按需生成值,你可以更高效地处理大型数据集,编写更简洁、更易维护的代码。

编写高效和可维护的Python函数对于生成高质量的代码至关重要。本文将向大家分享5个关键技巧(也是众多技术大牛多年以来的经验总结),帮助你完善Python函数并提高代码的可读性、可维护性和稳健性。

以下是文章的整体概览:

首先,强调遵循单一职责原则,确保每个函数只执行一个任务。接下来,讨论了类型提示(type hints)为增强代码清晰度和长期可维护性带来的好处。

接着文章探讨了仅关键字参数(keyword-only)这一Python功能,它可以通过强制使用参数名称来最小化出错的几率。另一个建议是严格控制参数数量,仅使用函数功能所必要的参数,这样做可以减少复杂性和潜在的 bug。

最后,文章倡导使用生成器(Generator)这种内存高效利用的技术来返回可迭代数据,而不是一次性构造并返回整个列表。

无论你是新手还是经验丰富的开发者,都建议从现在开始应用这5个技巧,并在日常工作中不断实践,相信你可以在未来的编程生涯中编写出更高效、更具可读性和可维护性的函数,从而产生更高质量的代码,从而提高你的生产力。

1. 你的函数应该只做一件事

“你的函数应该只做一件事”原则是整洁代码和高效编程的核心原则。这一原则也被称为单一职责原则(Single Responsibility Principle, SRP),它的宗旨是建议一个函数应该只有一个职责或任务。这会让你的代码更易于阅读、测试、调试和维护。以下是应用此原则的一些优点:

  • 可读性(Readability):当一个函数只做一件事时,会更容易理解,初一瞥就可以一目了然。函数名可以清楚地描述其目的(命名时尽可能做到见名知意👍),实现也很简单。
  • 可重用性(Reusability):单一目的的函数可以在程序的不同部分或其他项目中重复使用,避免重复造轮子。
  • 可测试性(Testability):编写针对一个单一功能函数的测试更容易,这种测试也更可靠。
  • 可维护性(Maintainability):如果一个函数只负责一个任务,影响该任务的需求变更将局限在该函数内,减少了代码其他部分出现bug的风险。

假设你正在开发一个Python程序,处理一个数字列表,需要实现:

  • 过滤掉负数。
  • 对剩余的数字进行平方运算。
  • 计算平方数的总和。
def filter_negative_numbers(numbers):
    """Filters out negative numbers from the list."""
    return [num for num in numbers if num >= 0]

def square_numbers(numbers):
    """Return a list of squared numbers."""
    return [num ** 2 for num in numbers]

def sum_numbers(numbers):
    """Return the sum of numbers."""
    return sum(numbers)

def data_processing(numbers):
    """Process the list of nubers: filter, square, and sum."""
    positive_numbers = filter_negative_numbers(numbers)
    squared_numbers = square_numbers(positive_numbers)
    total = sum_numbers(squared_numbers)
    return total

if __name__ == '__main__':
    numbers = [-2, -3, 4, -1, -2, 1, 5, -3]
    result = data_processing(numbers)
    print(result)   # Output: 42

2. 增加类型提示以提高可读性和可维护性

类型提示(Type hints)是Python中一项非常棒的功能,允许你指定变量、函数参数和返回值的预期类型。该特征在 PEP 484 中引入,类型提示不会影响程序运行时的行为,而是提供了一种对代码执行静态类型检查的方式。它们提高了代码的可读性,并帮助开发人员理解预期的输入和输出类型,使代码更易于维护。添加类型提示可以改善以下几点:

  • 提高可读性:通过明确指出函数期望的参数类型和返回值类型,类型提示使代码更具可读性,并且更容易理解,一目了然。
  • 错误检测:可以使用像 mypy 这样的工具进行静态类型检查,在开发过程的早期就捕捉潜在的 bug。
  • 文档:类型提示可以作为一种文档形式,提供有关如何使用函数和方法的宝贵信息。

在单一职责原则中我们提供了一个没有类型提示的示例,接下来我们将提供一个具有类型提示的版本,功能一样:

from typing import List

def filter_positive_numbers(numbers: List[int]) -> List[int]:
    """Filters out negative numbers from the list."""
    return [num for num in numbers if num >= 0]

def square_positive_numbers(numbers: List[int]) -> List[int]:
    """Return a list of squared numbers."""
    return [num ** 2 for num in numbers]

def sum_numbers(numbers: List[int]) -> int:
    """Return the sum of numbers."""
    return sum(numbers)

def data_processing(numbers: List[int]) -> int:
    """Process the list of nubers: filter, square, and sum."""
    positive_numbers = filter_positive_numbers(numbers)
    squared_numbers = square_positive_numbers(positive_numbers)
    total = sum_numbers(squared_numbers)
    return total

if __name__ == '__main__':
    numbers = [-2, -3, 4, -1, -2, 1, 5, -3]
    result = data_processing(numbers)
    print(result)   # Output: 42

比较这两段代码,我们可以发现:

  • 可读性(Readability)****

没有类型提示:阅读函数定义时,不太清楚期望什么类型的参数或函数返回什么类型。

有类型提示:函数签名明确指出它们使用整数列表,并返回整数列表或单个整数。

  • 错误检测(Error Detection)
  • 没有类型提示:类型相关的错误可能只会在运行时被捕获,这可能导致难以追踪的 bug。

  • 有类型提示:像 mypy 这样的工具可以在编译时检查类型,在代码执行之前捕捉错误。

3. 强制使用仅关键字参数以最小化出错几率

强制使用“仅关键字参数”(keyword-only)是Python中的一种技术,该技术表明,调用函数时某些参数必须通过名称指定。

这是通过在函数定义中使用特殊语法来完成的,该语法可以防止这些参数以位置方式传递。这种方法可以显著提高代码的清晰度并减少错误。

在Python函数中强制使用“仅关键字参数”可以大大增强代码的清晰度和正确性。关键字参数只能使用参数名称指定。这种强制执行有助于:

  • 防止错误:通过要求参数按名称传递,可以减少以错误顺序传递参数的风险,从而避免出现微不可查的 bug。
  • 提高可读性:它使函数调用更具可读性,明确表示每个参数的含义。
  • 增强灵活性:它允许你在不破坏现有代码的情况下,将来可以向函数添加更多参数,因为参数是显式命名的。
  • 提高清晰度:它使代码的意图更加清晰,因为每个参数的目的都在调用点指定。

下面是一个邮件发送的示例。send_email 函数接受一个可选的 cc 字符串:

def send_email(recipient: str, subject: str, body: str, cc: str = None):
    print(f"Sending email to {recipient}...")
    print(f"Subject: {subject}")
    print(f"Body: {body}")
    if cc:
        print(f"CC: {cc}")
        
if __name__ == '__main__':
    send_email('jackzhang@example.com', '编写高效Python函数的5个技巧', 
               '本文将向大家分享5个让你编写高效Python函数的技巧...', 'cc@example.com')

假设您想将可选的 cc 参数设置为关键字参数。可以这样做:

# Make the optional 'cc' argument keyword-only
def send_email(recipient: str, subject: str, body: str, *, cc: str = None):
    print(f"Sending email to {recipient}...")
    print(f"Subject: {subject}")
    print(f"Body: {body}")
    if cc:
        print(f"CC: {cc}")
        
if __name__ == '__main__':
    send_email('jackzhang@example.com', '编写高效Python函数的5个技巧', 
               '本文将向大家分享5个让你编写高效Python函数的技巧...', cc='cc@example.com')

只需要在可选参数 cc 前使用 * 号将前后隔开即可将其后的参数变成仅关键字参数。如果你想了解更多关于关键字参数和位置参数的细节,请阅读我的另一篇文章(Python效率秘籍:使用“*” 和“/” 让你的函数参数看起来更整洁)。

现在我们再次调用函数时可选参数 cc 就必须用定义时的名称进行值传递:

send_email('jackzhang@example.com', '编写高效Python函数的5个技巧', 
           '本文将向大家分享5个让你编写高效Python函数的技巧...', cc='cc@example.com')
Sending email to jackzhang@example.com...
Subject: 编写高效Python函数的5个技巧
Body: 本文将向大家分享5个让你编写高效Python函数的技巧...
CC: cc@example.com

如果我们尝试像之前一样所有参数都采用位置参数传递:

send_email('jackzhang@example.com', '编写高效Python函数的5个技巧', 
           '本文将向大家分享5个让你编写高效Python函数的技巧...', 'cc@example.com')

你将会收到下面这样的报错信息:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[6], line 10
      7         print(f"CC: {cc}")
      9 if __name__ == '__main__':
---> 10     send_email('jackzhang@example.com', '编写高效Python函数的5个技巧', 
     11                '本文将向大家分享5个让你编写高效Python函数的技巧...', 'cc@example.com')

TypeError: send_email() takes 3 positional arguments but 4 were given

4. 你的函数应该只使用必要参数

在定义函数时,严格限制参数数量,将其限制为函数操作所必要的参数至关重要。不必要的参数会常常会导致混淆,使函数调用臃肿,并且会让维护变得更复杂。以下是你应该只使用必要参数的几个原因:

  • 提高可读性:更少的参数使函数签名更简单、更易于理解。
  • 增强可维护性:参数较少的函数更易于重构和测试。
  • 减少错误:当函数只接受必要的参数时,可以降低传递不正确或冗余数据的可能性。

以下是一个冗余参数的函数示例:

# example function for processing an order including unnecessary arguments
def process_order(order_id: int, customer_id: int, customer_name: str,
                  amount: float, discount: float = 0.0):
    print(f"Processing order {order_id} for customer {customer_id} - {customer_name}")
    total_amount = amount * (1 - discount)
    print(f"Total amount after discount: {total_amount}")
    
if __name__ == '__main__':
    process_order(666, 888, 'Jack Zhang', 1000.0, 0.05)
Processing order 666 for customer 888 - Jack Zhang
Total amount after discount: 950.0

在这个例子中,函数 process_order 同时接受 customer_id 和 customer_name 参数,如果所有必需的信息都可以从 order_id 中获得,这两个参数可能是不必要的。

现在,让我们只使用必要的参数,重构该函数:

# example function for processing an order with only necessary arguments
def process_order(order_id: int, amount: float, discount: float = 0.0):
    print(f"Processing order {order_id}")
    total_amount = amount * (1 - discount)
    print(f"Total amount after discount: {total_amount}")
    
if __name__ == '__main__':
    process_order(666, 1000.0, 0.05)

5. 使用生成器返回列表

生成器(Generator)是Python中一种特殊的可迭代对象,它允许你逐个迭代序列值,而不需要一次性将整个序列存储在内存中。生成器通过函数和 yield 关键字进行定义,该关键字允许函数返回一个值并暂停其状态,在下次请求值时恢复。这使得生成器成为处理大型数据集或数据流的高效方式。

以下是你应该使用生成器的几个原因:

  • 内存效率:生成器一次只生成一个数据项,并且是按需生成,这意味着它们不需要将整个序列存储在内存中。这对于大型数据集特别有用。
  • 性能:由于生成器按需生成数据项,因此它们可以让程序性能得到提升,因为避免了创建和存储大型数据结构的开销。
  • 惰性求值:生成器按需计算值,这可以得到更高效和响应性更强的程序。
  • 更简单的代码:生成器可以简化创建迭代器所需的代码,使其更易于阅读和维护。

以下是一个不使用生成器返回列表的函数示例:

from typing import List

def get_squares(n: int) -> List[int]:
    """Return a list of squares from 0 to n-1."""
    squares = []
    for i in range(n):
        squares.append(i * i)
    return squares

if __name__ == '__main__':
    squares_list = get_squares(10)
    print(squares_list) 
    # Output: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

在这个例子中,函数 get_squares 在返回之前会生成并存储所有数值(0~n-1)的平方数。现在,我们改用生成器实现相同的功能:

from typing import List

def get_squares(n: int) -> List[int]:
    """Yield squares from 0 to n-1."""
    for i in range(n):
        yield i * i
        
if __name__ == '__main__':
    squares_gen = get_squares(10)
    print(list(squares_gen))

在Python中使用生成器返回列表可以在内存效率、性能和惰性求值方面带来显著优势。通过按需生成值,你可以更高效地处理大型数据集,编写更简洁、更易维护的代码。

两种方法的比较清楚地显示了使用生成器的好处,特别是对于大型或计算密集型序列的优势尤其显著。

责任编辑:武晓燕 来源: 数据派探险家
相关推荐

2021-02-23 10:48:30

Python代码开发

2024-02-26 16:40:58

2014-08-11 12:54:27

构建模块代码审查编程

2024-01-30 08:54:05

JavaScript技巧代码

2011-11-25 10:35:20

Java

2023-07-30 17:10:32

TypeScript开发

2023-02-26 23:23:36

CSS开发Web

2017-06-19 15:46:08

LinuxBash脚本技巧

2017-08-15 11:32:21

LinuxBash脚本技巧

2019-12-12 10:23:34

Linux 代码 开发

2010-09-06 09:06:22

CSS

2022-01-19 17:48:57

测试用例开发

2014-11-10 09:59:08

jQuery

2020-08-06 00:25:38

Python代码开发

2020-09-21 06:58:56

TS 代码建议

2023-07-06 14:37:05

2023-11-23 10:21:37

2022-05-04 12:44:57

Python编程语言

2020-09-25 16:20:21

Python内置函数字符串

2021-12-09 23:24:56

勒索软件攻击网络安全
点赞
收藏

51CTO技术栈公众号