百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

5个简单而有效的Python技巧,用于编写更好、更可维护的函数

off999 2024-10-23 12:40 27 浏览 0 评论

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

如您所见,使用生成器可以提高效率,尤其是对于较大的输入大小。此外,您可以将多个生成器链接在一起,这样您就可以使用生成器创建高效的数据处理管道。

相关推荐

大文件传不动?WinRAR/7-Zip 入门到高手,这 5 个技巧让你效率翻倍

“这200张照片怎么传给女儿?微信发不了,邮箱附件又超限……”62岁的张阿姨对着电脑犯愁时,儿子只用了3分钟就把照片压缩成一个文件,还教她:“以后用压缩软件,比打包行李还方便!”职场人更懂这...

电脑解压缩软件推荐——7-Zip:免费、高效、简洁的文件管理神器

在日常工作中,我们经常需要处理压缩文件。无论是下载软件包、接收文件,还是存储大量数据,压缩和解压缩文件都成为了我们日常操作的一部分。而说到压缩解压软件,7-Zip绝对是一个不可忽视的名字。今天,我就来...

设置了加密密码zip文件要如何打开?这几个方法可以试试~

Zip是一种常见的压缩格式文件,文件还可以设置密码保护。那设置了密码的Zip文件要如何打开呢?不清楚的小伙伴一起来看看吧。当我们知道密码想要打开带密码的Zip文件,我们需要用到适用于Zip格式的解压缩...

大文件想要传输成功,怎么把ZIP文件分卷压缩

不知道各位小伙伴有没有这样的烦恼,发送很大很大的压缩包会受到限制,为此,想要在压缩过程中将文件拆分为几个压缩包并且同时为所有压缩包设置加密应该如何设置?方法一:使用7-Zip免费且强大的文件管理工具7...

高效处理 RAR 分卷压缩包:合并解压操作全攻略

在文件传输和存储过程中,当遇到大文件时,我们常常会使用分卷压缩的方式将其拆分成多个较小的压缩包,方便存储和传输。RAR作为一种常见的压缩格式,分卷压缩包的使用频率也很高。但很多人在拿到RAR分卷...

2个方法教你如何删除ZIP压缩包密码

zip压缩包设置了加密密码,每次解压文件都需要输入密码才能够顺利解压出文件,当压缩包文件不再需要加密的时候,大家肯定想删除压缩包密码,或是忘记了压缩包密码,想要通过删除操作将压缩包密码删除,就能够顺利...

速转!漏洞预警丨压缩软件Winrar目录穿越漏洞

WinRAR是一款功能强大的压缩包管理器,它是档案工具RAR在Windows环境下的图形界面。该软件可用于备份数据,缩减电子邮件附件的大小,解压缩从Internet上下载的RAR、ZIP及其它类...

文件解压方法和工具分享_文件解压工具下载

压缩文件减少文件大小,降低文件失效的概率,总得来说好处很多。所以很多文件我们下载下来都是压缩软件,很多小伙伴不知道怎么解压,或者不知道什么工具更好,所以今天做了文件解压方法和工具的分享给大家。一、解压...

[python]《Python编程快速上手:让繁琐工作自动化》学习笔记3

1.组织文件笔记(第9章)(代码下载)1.1文件与文件路径通过importshutil调用shutil模块操作目录,shutil模块能够在Python程序中实现文件复制、移动、改名和删除;同时...

Python内置tarfile模块:读写 tar 归档文件详解

一、学习目标1.1学习目标掌握Python内置模块tarfile的核心功能,包括:理解tar归档文件的原理与常见压缩格式(gzip/bz2/lzma)掌握tar文件的读写操作(创建、解压、查看、过滤...

使用python展开tar包_python拓展

类Unix的系统,打包文件经常使用的就是tar包,结合zip工具,可以方便的打包并解压。在python的标准库里面有tarfile库,可以方便实现生成了展开tar包。使用这个库最大的好处,可能就在于不...

银狐钓鱼再升级:白文件脚本化实现GO语言后门持久驻留

近期,火绒威胁情报中心监测到一批相对更为活跃的“银狐”系列变种木马。火绒安全工程师第一时间获取样本并进行分析。分析发现,该样本通过阿里云存储桶下发恶意文件,采用AppDomainManager进行白利...

ZIP文件怎么打开?2个简单方法教你轻松搞定!

在日常工作和生活中,我们经常会遇到各种压缩文件,其中最常见的格式之一就是ZIP。ZIP文件通过压缩数据来减少文件大小,方便我们进行存储和传输。然而,对于初学者来说,如何打开ZIP文件可能会成为一个小小...

Ubuntu—解压多个zip压缩文件.zip .z01 .z02

方法将所有zip文件放在同一目录中:zip_file.z01,zip_file.z02,zip_file.z03,...,zip_file.zip。在Zip3.0版本及以上,使用下列命令:将所有zi...

如何使用7-Zip对文件进行加密压缩

7-Zip是一款开源的文件归档工具,支持多种压缩格式,并提供了对压缩文件进行加密的功能。使用7-Zip可以轻松创建和解压.7z、.zip等格式的压缩文件,并且可以通过设置密码来保护压缩包中的...

取消回复欢迎 发表评论: