网站首页 > 技术文章 正文
Python 中的函数可让编写模块化代码。当需要在多个地方执行一项任务时,可以将任务的逻辑包装到 Python 函数中。并且可以在每次需要执行该特定任务时调用该函数。虽然开始使用 Python 函数似乎很简单,但编写可维护且性能良好的函数并不那么简单。

一个函数只做一件事
在用 Python 编写函数时,人们常常会将所有相关任务放入一个函数中。虽然这可以帮助您快速编写代码,但在不久的将来,这只会使您的代码难以维护。这不仅会使理解函数的功能变得更加困难,还会导致其他问题,例如参数过多。
作为一种良好做法,应该始终让您数只做一件事(一项任务),并做好这件事。但有时,对于一项任务,可能需要完成一系列子任务。那么,如何决定是否以及如何重构该函数?
根据函数要执行的操作以及任务的复杂程度,可以确定子任务之间的关注点分离。然后确定一个合适的级别,可以在该级别将函数重构为多个函数 - 每个函数专注于特定的子任务。

这是一个例子。看一下这个函数analyze_and_report_sales:
# fn. to analyze sales data, calculate sales metrics, and write it to a file
def analyze_and_report_sales(data, report_filename):
total_sales = sum(item['price'] * item['quantity'] for item in data)
average_sales = total_sales / len(data)
with open(report_filename, 'w') as report_file:
report_file.write(f"Total Sales: {total_sales}\n")
report_file.write(f"Average Sales: {average_sales}\n")
return total_sales, average_sales
很容易看出它可以重构为两个函数:一个计算销售指标,另一个将销售指标写入文件,如下所示:
# refactored into two funcs: one to calculate metrics and another to write sales report
def calculate_sales_metrics(data):
total_sales = sum(item['price'] * item['quantity'] for item in data)
average_sales = total_sales / len(data)
return total_sales, average_sales
def write_sales_report(report_filename, total_sales, average_sales):
with open(report_filename, 'w') as report_file:
report_file.write(f"Total Sales: {total_sales}\n")
report_file.write(f"Average Sales: {average_sales}\n")
现在,可以更轻松地分别调试与销售指标计算和文件操作相关的任何问题。以下是示例函数调用:
data = [{'price': 100, 'quantity': 2}, {'price': 200, 'quantity': 1}]
total_sales, average_sales = calculate_sales_metrics(data)
write_sales_report('sales_report.txt', total_sales, average_sales)
应该能够在工作目录中看到包含销售指标的“sales_report.txt”文件。这是一个简单的入门示例,但它非常有用,尤其是在您处理更复杂的函数时。
2. 添加类型提示以提高可维护性
Python 是一种动态类型语言。因此,无需为创建的变量声明类型。但您可以添加类型提示来指定变量的预期数据类型。定义函数时,可以为参数和返回值添加预期数据类型。
由于 Python 不会在运行时强制类型,因此添加类型提示在运行时不会产生任何效果。但使用类型提示仍然有好处,尤其是在可维护性方面:
- 向 Python 函数添加类型提示可作为内联文档,并可以更好地了解函数的作用以及它使用和返回的值。
- 当向函数添加类型提示时,可以配置 IDE 以利用这些类型提示。因此,如果您尝试在一个或多个函数调用中传递无效类型的参数、实现返回值与预期类型不匹配的函数等,将收到有用的警告。因此,您可以提前将错误降到最低。
- 可以选择使用静态类型检查器(例如mypy)来尽早捕获错误,而不是让类型不匹配引入难以调试的细微错误。
以下是处理订单详情的函数:
# fn. to process orders
def process_orders(orders):
total_quantity = sum(order['quantity'] for order in orders)
total_value = sum(order['quantity'] * order['price'] for order in orders)
return {
'total_quantity': total_quantity,
'total_value': total_value
}
现在向函数添加类型提示,如下所示:
# modified with type hints
from typing import List, Dict
def process_orders(orders: List[Dict[str, float | int]]) -> Dict[str, float | int]:
total_quantity = sum(order['quantity'] for order in orders)
total_value = sum(order['quantity'] * order['price'] for order in orders)
return {
'total_quantity': total_quantity,
'total_value': total_value
}
通过修改后的版本,可以知道该函数接受一个字典列表。字典的键应全部为字符串,值可以是整数或浮点值。该函数还返回一个字典。现在进行一个示例函数调用:
# Sample data
orders = [
{'price': 100.0, 'quantity': 2},
{'price': 50.0, 'quantity': 5},
{'price': 150.0, 'quantity': 1}
]
# Sample function call
result = process_orders(orders)
print(result)
输出如下:
{'total_quantity': 8, 'total_value': 600.0}
在此示例中,类型提示可帮助我们更好地了解函数的工作原理。
3. 只接受你真正需要的参数
如果是初学者或刚刚开始从事第一个开发角色,那么在定义函数签名时考虑不同的参数非常重要。确保函数只接受实际需要的参数,可以让函数调用更简洁、更易于维护。另外,函数中的参数过多也会使维护变得困难。那么,如何定义具有适当数量参数的易于维护的函数呢?
如果发现编写的函数的参数数量越来越多,第一步是从函数中删除所有未使用的参数。如果即使执行此步骤后参数仍然太多,请返回提示 1:将任务分解为多个子任务,并将函数重构为多个较小的函数。这将有助于控制参数数量。

现在来看一个简单的例子。这里计算学生成绩的函数定义包含instructor从未使用过的参数:
# takes in an arg that's never used!
def process_student_grades(student_id, grades, course_name, instructor'):
average_grade = sum(grades) / len(grades)
return f"Student {student_id} achieved an average grade of {average_grade:.2f} in {course_name}."
可以像这样重写不带参数的函数instructor:
# better version!
def process_student_grades(student_id: int, grades: list, course_name: str) -> str:
average_grade = sum(grades) / len(grades)
return f"Student {student_id} achieved an average grade of {average_grade:.2f} in {course_name}."
# Usage
student_id = 12345
grades = [85, 90, 75, 88, 92]
course_name = "Mathematics"
result = process_student_grades(student_id, grades, course_name)
print(result)
这是函数调用的输出:
Student 12345 achieved an average grade of 86.00 in Mathematics.
4. 强制使用关键字参数以最大程度地减少错误
实际上,大多数 Python 函数都接受多个参数。您可以将参数作为位置参数、关键字参数或两者的混合传递给 Python 函数。有些参数天生就是位置参数。但有时函数调用只包含位置参数可能会造成混淆。当函数接受多个相同数据类型的参数时尤其如此,有些参数是必需的,有些是可选的。
使用位置参数时,参数会按照它们在函数调用中出现的顺序传递给函数签名中的参数。因此,参数顺序的变化可能会引入细微的错误和类型错误。
将可选参数设为仅关键字通常很有帮助。这也使得添加可选参数变得更加容易 — 而不会破坏现有调用。
这是一个例子。该process_payment函数接受一个可选description字符串:
# example fn. for processing transaction
def process_payment(transaction_id: int, amount: float, currency: str, description: str = None):
print(f"Processing transaction {transaction_id}...")
print(f"Amount: {amount} {currency}")
if description:
print(f"Description: {description}")
假设想让可选参数成为description仅关键字参数。可以这样做:
# enforce keyword-only arguments to minimize errors
# make the optional `description` arg keyword-only
def process_payment(transaction_id: int, amount: float, currency: str, *, description: str = None):
print(f"Processing transaction {transaction_id}:")
print(f"Amount: {amount} {currency}")
if description:
print(f"Description: {description}")
现在来看一个示例函数调用:
process_payment(1234, 100.0, 'USD', description='Payment for services')
输出如下:
Processing transaction 1234...
Amount: 100.0 USD
Description: Payment for services
现在尝试将所有参数作为位置传递:
# throws error as we try to pass in more positional args than allowed!
process_payment(5678, 150.0, 'EUR', 'Invoice payment')
将收到如下所示的错误:
Traceback (most recent call last):
File "/home/balapriya/better-fns/tip4.py", line 9, in
process_payment(1234, 150.0, 'EUR', 'Invoice payment')
TypeError: process_payment() takes 3 positional arguments but 4 were given
5. 不要从函数返回列表;改用生成器
编写生成序列(例如值列表)的 Python 函数很常见。但应尽可能避免从 Python 函数返回列表。相反,可以将它们重写为生成器函数。生成器使用惰性求值;因此它们会按需生成序列的元素,而不是提前计算所有值。举例来说,以下函数可生成达到一定上限的斐波那契数列:
# returns a list of Fibonacci numbers
def generate_fibonacci_numbers_list(limit):
fibonacci_numbers = [0, 1]
while fibonacci_numbers[-1] + fibonacci_numbers[-2] <= limit:
fibonacci_numbers.append(fibonacci_numbers[-1] + fibonacci_numbers[-2])
return fibonacci_numbers
这是一个递归实现,计算量很大,填充列表并返回列表似乎比必要的更冗长。以下是使用生成器的函数的改进版本:
# use generators instead
from typing import Generator
def generate_fibonacci_numbers(limit: int) -> Generator[int, None, None]:
a, b = 0, 1
while a <= limit:
yield a
a, b = b, a + b
在这种情况下,该函数返回一个生成器对象,然后您可以循环遍历该对象以获取序列的元素:
limit = 100
fibonacci_numbers_generator = generate_fibonacci_numbers(limit)
for num in fibonacci_numbers_generator:
print(num)
输出如下:
0
1
1
2
3
5
8
13
21
34
55
89
如您所见,使用生成器可以提高效率,尤其是对于较大的输入大小。此外,您可以将多个生成器链接在一起,这样您就可以使用生成器创建高效的数据处理管道。
- 上一篇: Python应用实战,用动画生成冒泡排序的过程
- 下一篇: Python的分支结构和循环结构
猜你喜欢
- 2024-12-27 python编程学习系列:求一列数的最大值和最小值
- 2024-12-27 记住这份软件测试八股文还怕不能拿offer?你值得拥有
- 2024-12-27 Python的分支结构和循环结构
- 2024-12-27 Python应用实战,用动画生成冒泡排序的过程
- 2024-12-27 Python基础之:Python中的模块
- 2024-12-27 写 Python 代码不可不知的函数式编程技术
- 2024-12-27 多种方法在Python中输出斐波那契数列
- 2024-12-27 学好了Python,我们可以实现很多算法了
- 2024-12-27 一套真实的Python面试题,几十个题目汇总
- 2024-12-27 Python 生成器函数:深入理解与应用案例
- 05-25Python 3.14 t-string 要来了,它与 f-string 有何不同?
- 05-25Python基础元素语法总结
- 05-25Python中的变量是什么东西?
- 05-25新手常见的python报错及解决方案
- 05-2511-Python变量
- 05-2510个每个人都是需要知道Python问题
- 05-25Python编程:轻松掌握函数定义、类型及其参数传递方式
- 05-25Python基础语法
- 257℃Python短文,Python中的嵌套条件语句(六)
- 257℃python笔记:for循环嵌套。end=""的作用,图形打印
- 256℃PythonNet:实现Python与.Net代码相互调用!
- 251℃Python操作Sqlserver数据库(多库同时异步执行:增删改查)
- 251℃Python实现字符串小写转大写并写入文件
- 106℃原来2025是完美的平方年,一起探索六种平方的算吧
- 90℃Python 和 JavaScript 终于联姻了!PythonMonkey 要火?
- 81℃Ollama v0.4.5-v0.4.7 更新集合:Ollama Python 库改进、新模型支持
- 最近发表
- 标签列表
-
- python中类 (31)
- python 迭代 (34)
- python 小写 (35)
- python怎么输出 (33)
- python 日志 (35)
- python语音 (31)
- python 工程师 (34)
- python3 安装 (31)
- python音乐 (31)
- 安卓 python (32)
- python 小游戏 (32)
- python 安卓 (31)
- python聚类 (34)
- python向量 (31)
- python大全 (31)
- python次方 (33)
- python桌面 (32)
- python总结 (34)
- python浏览器 (32)
- python 请求 (32)
- python 前端 (32)
- python验证码 (33)
- python 题目 (32)
- python 文件写 (33)
- python中的用法 (32)