当前位置: 首页 > 科技观察

Python函数的模块化

时间:2023-03-15 13:03:34 科技观察

您是否对函数、类、方法、库和模块等花哨的编程术语感到困惑?您是否正在为变量作用域而苦苦挣扎?无论您是自学成才还是经过正式培训的程序人员,代码的模块化都会让人感到困惑。但是类和库鼓励模块化代码,因为模块化代码意味着你只需要构建一个多用途代码块的集合,可以在很多项目中使用,减少编码工作量。换句话说,如果您遵循本文对Python函数的研究,您将找到更聪明地工作的方法,这意味着更少的工作。本文假设您熟悉Python(LCTT译注:略熟悉),能够编写并运行简单的脚本。如果你没有用过Python,请先阅读我的文章:Python简介。函数函数是迈向模块化的重要一步,因为它们是形式化的重复方法。如果在您的程序中,有一个任务需要重复执行,那么您可以将代码放在一个函数中,并根据需要经常调用该函数。这样,您只需要编写一次代码并随心所欲地使用它。下面是一个简单函数的例子:#!/usr/bin/envpython3importtimedefTimer():print("Timeis"+str(time.time()))创建一个名为mymodularity的目录,将上面的函数代码保存为这个目录时间戳.py下。除了这个功能之外,在mymodularity目录中创建一个名为__init__.py的文件,您可以在文件管理器或bashshell中执行此操作:$touchmymodularity/__init__.py现在,您已经创建了自己的Python库(称为“模块”"在Python中),名为mymodularity。它不是一个特别有用的模块,因为它所做的只是导入时间模块并打印时间戳,但这只是一个开始。要使用您的函数,请像对待任何其他Python模块一样对待它。这是一个小应用程序,它使用您的mymodularity包来测试Pythonsleep()函数的准确性。将此文件另存为sleeptest.py,放在mymodularity文件夹之外,因为如果您将它保存在mymodularity中,那么它将成为您的包中的一个模块,而这是您不需要的。#!/usr/bin/envpython3importtimefrommymodularityimporttimestampprint("TestingPythonsleep()...")#modularitytimestamp.Timer()time.sleep(3)timestamp.Timer()在这个简单的脚本中,你调用了mymodularity包中的时间戳模块两次.从包中导入模块时,通常的语法是从包中导入你需要的模块,然后使用模块名+一个点+要调用的函数名(例如timestamp.Timer())。您调用了Timer()函数两次,因此如果您的时间戳模块比这个简单的示例更复杂,您将节省大量重复代码。保存文件并运行:$python3./sleeptest.pyTestingPythonsleep()...Timeis1560711266.1526039Timeis1560711269.1557732经测试,Python中的sleep函数非常准确:等待三秒后时间戳成功正确递增3,以微秒为单位的差异非常小。Python库的结构可能看起来很混乱,但这并不神奇。Python被编程为一个包含Python代码的目录,附带一个__init__.py文件,那么这个目录就被当作一个包,Python会先在当前目录下寻找可用的模块。这就是为什么frommymodularityimporttimestamp语句起作用的原因:Python在当前目录中查找名为mymodularity的目录,然后查找timestamp.py文件。您在此示例中所做的与非模块化版本相同:#!/usr/bin/envpython3importtimefrommymodularityimporttimestampprint("TestingPythonsleep()...")#nomodularityprint("Timeis"+str(time.time()))time.sleep(3)print("Timeis"+str(time.time()))这么简单的例子,并不是真的需要这样写测试,而是为了写自己的模块到说的好实践是您的代码是通用的,您可以将其重用于其他项目。您可以通过在调用函数时传递信息来使您的代码更通用。例如,假设你想用模块测试的不是系统的sleep函数,而是用户自己的sleep函数,更改timestamp的代码,让它接受一个传入的变量msg,它会是一个字符串,控制如何显示timestamp每次调用:#!/usr/bin/envpython3importtime#更新代码defTimer(msg):print(str(msg)+str(time.time()))现在函数比以前更抽象了。它仍然打印时间戳,但它在msg中为用户打印的内容仍然未定义。这意味着您需要在调用函数时定义它。Timer函数接受的msg参数是任意命名的,您可以使用参数m、message或text,或任何您认为有意义的参数。重要的是,当调用timestamp.Timer函数时,它接收一个文本作为输入,将接收到的任何内容放入msg变量,并使用该变量来完成它的工作。这是一个测试用户正确感知时间流逝能力的新程序:#!/usr/bin/envpython3frommymodularityimporttimestampprint("PresstheRETURNkey.Countto3,andpressRETURNAgain.")input()timestamp.Timer("Startedtimerat")print("Countto3...")input()timestamp.Timer("Yousleptuntil")将您的新程序保存为response.py,运行它:$python3./response.py按RETURNkey.Countto3,然后按RETURNagain.Startedtimerat1560714482.3772075Countto3...Yousleptuntil1560712484.3函数和必需参数新版本的时间戳模块现在需要一个msg参数。这很重要,因为您的第一个应用程序不会运行,因为它没有将字符串传递给timestamp.Timer函数:$python3./sleeptest.pyTestingPythonsleep()...Traceback(mostrecentcallast):File"./sleeptest.py",line8,intimestamp.Timer()TypeError:Timer()missing1requiredpositionalargument:'msg'你能修复你的sleeptest.py应用程序,让它在更新的模块中正确运行吗?变量和函数按照设计,函数限制了变量的范围。换句话说,如果在函数内部创建变量,则该变量仅在该函数内起作用。如果您尝试在函数外部使用出现在函数内部的变量,则会发生错误。这是对response.py应用程序的修改,它尝试从timestamp.Timer()函数外部打印msg变量:("Startedtimerat")print("Countto3...")input()timestamp.Timer("Yousleptfor")print(msg)尝试运行一下,看到错误:$python3./response.py按RETURNkey.Countto3,并按RETURNagain.Startedtimerat1560719527.7862902Countto3...Yousleptfor1560719528.135406Traceback(mostrecentcallast):File"./response.py",line15,inprint(msg)NameError:name'msg'isnotdefined应用程序返回了NameError消息因为未定义味精。这看起来令人困惑,因为您编写了定义msg的代码,但您对代码的了解比Python多。调用函数的代码,不管函数是出现在同一个文件中,还是被打包成一个模块,都不知道函数内部发生了什么。函数独立执行其计算并返回您希望它返回的内容。其中涉及的任何变量都只是局部的:它们只存在于函数中,并且只在函数需要完成其目的时存在。Return语句如果您的应用程序需要专门包含在函数中的信息,请使用return语句让函数在运行后返回有意义的数据。时间就是金钱,所以修改时间戳函数以与虚构的计费系统一起工作:#!/usr/bin/envpython3importtimedefTimer(msg):print(str(msg)+str(time.time()))charge=.02returnchargetimestamp模块现在每次通话收费2美分,但最重要的是,它会返回每次通话收费的金额。下面是如何使用return语句的演示:#!/usr/bin/envpython3frommymodularityimporttimestampprint("PressRETURNforthetime(costs2cents).")print("PressQRETURNtoquit.")total=0whileTrue:kbd=input()ifkbd.lower()="q":print("Youowe$"+str(total))exit()else:charge=timestamp.Timer("Timeis")total=total+charge本示例代码中变量charge为timestamp.Timer()function的返回,它接收函数返回的任何内容。在这种情况下,该函数返回一个数字,因此使用一个名为total的新变量来跟踪已进行了多少更改。当应用程序收集到要退出的信息时,它会打印总费用:$python3./收费。如果您只是为一项任务编写一个简短的脚本,那么将函数编写在同一个文件中可能更有意义。唯一的区别是您不必导入自己的模块,但函数的工作方式相同。这是时间测试应用程序的最新迭代:#!/usr/bin/envpython3importtimetotal=0defTimer(msg):print(str(msg)+str(time.time()))charge=.02returnchargeprint("PressRETURNforthetime(costs2cents).")print("PressQRETURNtoquit.")whileTrue:kbd=input()ifkbd.lower()=="q":print("Youowe$"+str(total))exit()else:charge=Timer("Timeis")total=total+charge没有外部依赖(time模块包含在Python发行版中)并产生与模块化版本相同的结果。它的优点是一切都在一个文件中,缺点是您不能在其他脚本中使用Timer()函数,除非您手动复制并粘贴它。在函数外部创建的变量没有作用域限制,因此被认为是全局变量。全局变量的一个示例是charge.py中用于跟踪当前成本的total变量。total是在函数外部创建的,因此它绑定到应用程序而不是特定函数。应用程序中的函数可以访问全局变量,但要将变量传递到导入的模块中,您必须像发送msg变量一样将变量传递到模块中。全局变量很方便,因为它们似乎随处可用,但也很难跟踪它们,并且很难知道哪些变量不再需要但仍留在系统内存中(尽管Python有非常好的垃圾收集机制).但是,全局变量很重要,因为并非所有变量都可以是函数或类的局部变量。既然您知道如何将变量传递给函数并取回它们,事情就容易多了。总之,您已经学到了很多关于函数的知识,所以开始将它们放入您的脚本中——如果它不是作为单独的模块,那么作为您不必在一个脚本中编写多次的代码块。在本系列的下一篇文章中,我将介绍Python类。