使用 Python 函数实现模块化

通过使用 Python 函数处理重复性任务,最大限度地减少您的编码工作量。
146 位读者喜欢这篇文章。
OpenStack source code (Python) in VIM

Alex Sanchez. CC BY-SA 4.0.

您是否对诸如函数、类、方法、库和模块等花哨的编程术语感到困惑?您是否对变量的作用域感到困扰? 无论您是自学成才的程序员还是受过正式培训的代码猴子,代码的模块化都可能令人困惑。 但是,类和库鼓励模块化代码,而模块化代码意味着构建一个多用途代码块的集合,您可以在多个项目中使用它们,以减少您的编码工作量。 换句话说,如果您按照本文学习 Python 函数,您将找到更智能的工作方式,而更智能的工作意味着更少的工作。

本文假设您已经足够熟悉 Python,可以编写和运行简单的脚本。 如果您没有使用过 Python,请先阅读我的 Python 入门文章。

函数

函数是实现模块化的重要一步,因为它们是重复的正式方法。 如果您的程序中需要一遍又一遍地完成某项任务,则可以将代码分组到一个函数中,并根据需要经常调用该函数。 这样,您只需编写一次代码,但可以根据需要经常使用它。

这是一个简单函数的例子

#!/usr/bin/env python3

import time 

def Timer():
	print("Time is " + str(time.time() ) )

创建一个名为 mymodularity 的文件夹,并将函数代码保存为 timestamp.py

除了此函数之外,还在 mymodularity 目录中创建一个名为 __init__.py 的文件。 您可以在文件管理器或 Bash shell 中执行此操作

$ touch mymodularity/__init__.py

您现在已经在名为 mymodularity 的 Python 包中创建了自己的 Python 库(用 Python 行话说,称为“模块”)。 它不是一个非常有用的模块,因为它所做的只是导入 time 模块并打印时间戳,但这只是一个开始。

要使用您的函数,请像使用任何其他 Python 模块一样对待它。 这是一个小型应用程序,它使用您的 mymodularity 包来支持,测试 Python 的 sleep() 函数的准确性。 将此文件另存为 sleeptest.py mymodularity 目录之外 (如果您将此文件放入 mymodularity 中,那么它将成为您包中的一个模块,而您不希望这样)。

#!/usr/bin/env python3

import time
from mymodularity import timestamp

print("Testing Python sleep()...")

# modularity
timestamp.Timer()
time.sleep(3)
timestamp.Timer()

在这个简单的脚本中,您正在从 mymodularity 包中调用您的 timestamp 模块(两次)。 当您从一个包中导入一个模块时,通常的语法是从该包中导入您想要的模块,然后使用 *模块名称 + 一个点 + 您想要调用的函数的名称*(例如, timestamp.Timer())。

您正在调用您的 Timer() 函数两次,所以如果您的 timestamp 模块比这个简单的例子更复杂,那么您将节省大量的重复代码。

保存文件并运行它

$ python3 ./sleeptest.py
Testing Python sleep()...
Time is 1560711266.1526039
Time is 1560711269.1557732

根据您的测试,Python 中的 sleep 函数非常准确:经过三秒钟的睡眠后,时间戳已成功且正确地增加了三秒钟,并且在微秒方面略有差异。

Python 库的结构可能看起来令人困惑,但它不是魔术。 Python 被编程为将一个装满 Python 代码的文件夹和一个 __init__.py 文件视为一个包,并且被编程为首先在其当前目录中查找可用的模块。 这就是 from mymodularity import timestamp 语句起作用的原因:Python 在当前目录中查找一个名为 mymodularity 的文件夹,然后查找一个以 .py 结尾的 timestamp 文件。

在此示例中所做的事情在功能上与以下不太模块化的版本相同

#!/usr/bin/env python3

import time
from mymodularity import timestamp

print("Testing Python sleep()...")

# no modularity
print("Time is " + str(time.time() ) )
time.sleep(3)
print("Time is " + str(time.time() ) )

对于像这样的简单示例,实际上没有理由不以这种方式编写您的 sleep 测试,但是编写您自己的模块的最好之处在于,您的代码是通用的,因此您可以将其用于其他项目。

您可以通过在调用函数时将信息传递到函数中,使代码更加通用。 例如,假设您想使用您的模块来测试不是计算机的 sleep 函数,而是用户的 sleep 函数。 更改您的 timestamp 代码,使其接受一个名为 msg 的传入变量,该变量将是一个文本字符串,用于控制每次调用时 timestamp 的显示方式

#!/usr/bin/env python3

import time 

# updated code
def Timer(msg):
    print(str(msg) + str(time.time() ) )

现在您的函数比以前更抽象了。 它仍然打印一个时间戳,但它为用户打印的内容是不确定的。 这意味着您需要在调用该函数时对其进行定义。

您的 Timer 函数接受的 msg 参数是任意命名的。 您可以调用该参数 mmessagetext 或对您有意义的任何东西。 重要的是,当调用 timestamp.Timer 函数时,它接受一些文本作为其输入,将它接收到的任何内容放入一个变量中,并使用该变量来完成其任务。

这是一个新的应用程序,用于测试用户正确感知时间流逝的能力

#!/usr/bin/env python3

from mymodularity import timestamp

print("Press the RETURN key. Count to 3, and press RETURN again.")

input()
timestamp.Timer("Started timer at ")

print("Count to 3...")

input()
timestamp.Timer("You slept until ")

将您的新应用程序另存为 response.py 并运行它

$ python3 ./response.py 
Press the RETURN key. Count to 3, and press RETURN again.

Started timer at 1560714482.3772075
Count to 3...

You slept until 1560714484.1628013

函数和必需参数

您的 timestamp 模块的新版本现在需要一个 msg 参数。 这很重要,因为您的第一个应用程序已损坏,因为它没有将字符串传递给 timestamp.Timer 函数

$ python3 ./sleeptest.py
Testing Python sleep()...
Traceback (most recent call last):
  File "./sleeptest.py", line 8, in <module>
    timestamp.Timer()
TypeError: Timer() missing 1 required positional argument: 'msg'

您可以修复您的 sleeptest.py 应用程序,使其与模块的更新版本一起正确运行吗?

变量和函数

通过设计,函数限制了变量的作用域。 换句话说,如果在函数中创建了一个变量,则该变量对该函数可用。 如果您尝试使用函数外部的函数中出现的变量,则会发生错误。

这是对 response.py 应用程序的修改,尝试从 timestamp.Timer() 函数打印 msg 变量

#!/usr/bin/env python3

from mymodularity import timestamp

print("Press the RETURN key. Count to 3, and press RETURN again.")

input()
timestamp.Timer("Started timer at ")

print("Count to 3...")

input()
timestamp.Timer("You slept for ")

print(msg)

尝试运行它以查看错误

$ python3 ./response.py 
Press the RETURN key. Count to 3, and press RETURN again.

Started timer at 1560719527.7862902
Count to 3...

You slept for 1560719528.135406
Traceback (most recent call last):
  File "./response.py", line 15, in <module>
    print(msg)
NameError: name 'msg' is not defined

应用程序返回一个 NameError 消息,因为 msg 未定义。 这可能看起来令人困惑,因为您编写了定义 msg 的代码,但是您对您的代码的了解比 Python 更多。 调用函数的代码,无论该函数出现在同一文件中,还是将其打包为模块,都不知道函数内部发生了什么。 函数独立地执行其计算并返回已编程要返回的内容。 涉及的任何变量都仅是本地的:它们仅存在于函数内部,并且仅在函数完成其目的所需的时间内存在。

Return 语句

如果您的应用程序需要仅包含在函数中的信息,请使用 return 语句使函数在运行后提供有意义的数据。

他们说时间就是金钱,因此修改您的时间戳函数以允许使用一个虚拟的收费系统

#!/usr/bin/env python3

import time 

def Timer(msg):
    print(str(msg) + str(time.time() ) )
    charge = .02
    return charge

timestamp 模块现在每次调用收费两美分,但最重要的是,它每次调用都会返回收取的金额。

这是一个 return 语句如何使用的演示

#!/usr/bin/env python3

from mymodularity import timestamp

print("Press RETURN for the time (costs 2 cents).")
print("Press Q RETURN to quit.")

total = 0

while True:
    kbd = input()
    if kbd.lower() == "q":
        print("You owe $" + str(total) )
        exit()
    else:
        charge = timestamp.Timer("Time is ")
        total = total+charge

在此示例代码中,变量 charge 被赋值为 timestamp.Timer() 函数的终点,因此它接收函数返回的任何内容。 在这种情况下,函数返回一个数字,因此使用一个名为 total 的新变量来跟踪已进行的更改次数。 当应用程序收到退出信号时,它将打印总费用

$ python3 ./charge.py 
Press RETURN for the time (costs 2 cents).
Press Q RETURN to quit.

Time is 1560722430.345412

Time is 1560722430.933996

Time is 1560722434.6027434

Time is 1560722438.612629

Time is 1560722439.3649364
q
You owe $0.1

内联函数

函数不必在单独的文件中创建。 如果您只是编写一个特定于一项任务的简短脚本,那么在同一文件中编写您的函数可能更有意义。 唯一的区别是您不必导入自己的模块,否则该函数的工作方式相同。 这是时间测试应用程序的最新迭代作为一个文件

#!/usr/bin/env python3

import time 

total = 0

def Timer(msg):
    print(str(msg) + str(time.time() ) )
    charge = .02
    return charge

print("Press RETURN for the time (costs 2 cents).")
print("Press Q RETURN to quit.")

while True:
    kbd = input()
    if kbd.lower() == "q":
        print("You owe $" + str(total) )
        exit()
    else:
        charge = Timer("Time is ")
        total = total+charge

它没有外部依赖项(Python 发行版中包含 time 模块),并且产生与模块化版本相同的结果。 优点是所有内容都位于一个文件中,缺点是除非手动复制并粘贴,否则您无法在您正在编写的其他脚本中使用 Timer() 函数。

全局变量

在函数外部创建的变量没有任何限制其作用域,因此被认为是全局变量。

全局变量的一个示例是用于跟踪当前费用的 charge.py 示例中的 total 变量。 运行总计是在任何函数之外创建的,因此它绑定到应用程序,而不是绑定到特定的函数。

应用程序中的函数可以访问您的全局变量,但是要将该变量引入到导入的模块中,您必须以发送 msg 变量的相同方式将其发送到那里。

全局变量很方便,因为它们似乎随时随地可用,但是很难跟踪它们的作用域,并且知道哪些变量在不再需要后仍然长时间地停留在系统内存中(尽管 Python 通常具有非常好的垃圾回收)。

但是,全局变量很重要,因为并非所有变量都可以是函数或类的本地变量。 现在您知道如何将变量发送到函数并取回值,这很容易。

封装函数

您已经学了很多关于函数的知识,所以开始将它们放入您的脚本中——如果不是作为单独的模块,那么作为您不必在一个脚本中多次编写的代码块。 在本系列的下一篇文章中,我将介绍 Python 类。

接下来要阅读的内容
标签
Seth Kenlon
Seth Kenlon 是一位 UNIX 极客、自由文化倡导者、独立多媒体艺术家和 D&D 书呆子。 他曾在电影和计算行业工作,通常同时从事这两个行业。

6 条评论

这太棒了,Seth。 您已经说明并解释了很多我在其他任何地方都看不到的东西。 这是我见过的关于函数的最好文章。

谢谢,Don。 我希望它能帮助世界各地的新程序员,因为——哇,如果我在刚开始的时候就能得到这些信息就好了!

回复 作者 Don Watkins

我仍然在局部变量和全局变量方面犯一些小错误。 这很有见地。

不错

好文章。 Python文章是我的最爱!

非常好的文章。 我也有几个问题。
是否可以在模块内部创建模块?
是否可以在函数内部创建函数?
如何使用这些分层函数和模块?

© . All rights reserved.