1。Module1.Modulesandimports当程序代码量变得相当大,逻辑结构变得非常复杂时,我们必须将代码按照逻辑和功能划分成一些有组织的代码块,并将它们分成单独的文件保存。这些文件可以包含可执行代码、函数、类或这些的组合。这些独立且有组织的代码块称为模块。模块是Python代码的最高层组织单元。模块往往对应物理机上的Python文件(或用C、Java或C#等外部语言编写的扩展)。创建Python源文件时,对应的模块名是不带.py后缀的文件名。创建一个模块(Python程序文件)后,可以使用import语句从另一个文件中导入这个模块,从而实现代码复用。这种将其他模块附加到您的代码的行为称为导入。导入其他模块后,可以使用导入模块中定义的变量名。2、模块的作用代码复用由于模块对应Python文件,模块中的代码可以安全保存。您可以在代码中多次使用导入模块中定义的变量名(函数、类等),甚至可以重新导入模块。模块(和模块包)除了是Python代码的最高层次组织之外,也是Python中程序代码重用的最高层次。系统命名空间的划分模块仍然是定义变量名的空间,其内部定义的变量名作为模块的属性,通过导入可以被多个外部文件中的代码引用。模块将变量名封装到自己的命名空间中,有助于避免变量名冲突。一切都存在于一个“模块”中,可执行代码和创建的对象无疑都封装在模块中。正因为如此,模块是组织系统组件的自然工具。实现共享服务和数据从操作的角度来看,模块可以方便地实现跨系统共享的组件,只需在不同的文件中导入相同的模块即可。3.Python程序架构一个Python程序通常涉及多个文件,一般采用多文件系统的形式。即使您编写单个文件,您也几乎肯定会导入标准库模块或使用其他人已经编写的外部文件。一般来说,一个完整的程序由一个启动和运行的脚本文件,以及零个或多个支持(导入)文件组成。在Python中,顶级文件包含程序的主要控制流程:这是您需要运行以启动程序的文件。作为模块导入的文件通常不需要在运行时直接做任何事情,它提供顶层文件需要运行的各种组件(普通变量、函数、类等)。顶级文件使用模块文件中定义的组件,而那些模块使用其他模块定义的组件。在Python中,文件导入模块以访问模块中定义的变量,这些变量被视为模块的属性。导入的概念贯穿于整个Python。任何文件都可以从任何其他文件导入其变量,只要导入链需要深入即可。4.标准库模块Python带有许多有用的模块,称为标准链接库。这个合集有200多个模块,包括与平台无关的通用编程(不依赖于具体系统,在任何系统上都可以用同样的方式调用,也就是说,这些标准库模块是交叉的平台)任务:操作系统接口、目标文件保存、文本模式匹配、网络和Internet脚本、GUI构建等。这些工具都不是Python语言的一部分,但您可以导入适当的模块以在任何环境中使用它们安装Python的位置。由于这些是标准库模块,它们可以保证工作,并且可以在大多数执行Python的平台上运行。2.模块导入模块中的代码会在最终导入时执行。首先创建一个空的模块对象,然后从头到尾依次执行模块中的语句。由顶层(不在def或class内)赋值语句(例如=、def和class等)生成的变量将成为模块对象的属性,这些变量名将存储在模块的命名空间中。模块的命名空间可以通过属性__dict__或内置函数dir()获得。1.模块文件的命名任何以“.py”为后缀的Python文件都会自动被认为是一个Python模块。一般来说,Python文件的名字随便起,但是如果你打算将它作为一个模块导入,那么文件的末尾必须以“.py”开头。.py后缀对于执行但未导入的顶级文件在技术上是可选的,但每次添加它可确保文件类型更加可见,并使得以后可以将其导入到任何文件中。因为模块名称在Python程序中成为变量名称(没有.py)。因此,Python文件应该遵循普通变量名的命名规则。事实上,包导入中使用的模块的文件名和目录名都必须遵循变量名规则。2.导入模块的步骤在Python中,导入并不是将一个文件的文本插入到另一个文件中。导入实际上是一个运行时计算。程序第一次导入指定文件时,会执行三个步骤。(1)搜索找到模块文件。(2)编译成字节码(需要时)。(3)执行模块的代码,创建它定义的对象,并在import语句所在文件作用域的局部命名空间中定义一个或多个变量名。这三个步骤仅在第一次导入模块时执行。之后再导入同一个模块时,跳过这三个步骤,只取内存中加载的模块对象。这是有意为之的,因为操作成本很高。如果在加载模块后需要重新导入模块(例如,支持最终用户自定义),则必须通过调用reload()强制导入模块。从技术上讲,Python将加载的模块存储在名为sys.modules的表中,并在导入操作开始时检查该表。如果模块不存在,会自动执行以上三步。搜索Python遍历模块搜索路径,寻找import语句引用的模块文件。在importer文件中,只能列出要导入的模块文件的简单名称,而故意省略了路径和后缀。导入模块时,Python会将程序内部的模块名映射到外部物理环境中的文件名,即在模块搜索路径中的目录路径前加上模块名,模块名后加上.py或其他后缀。编译找到模块文件后,Python会寻找对应的.pyc字节码文件。如果没有字节码文件,Python会把模块文件编译成字节码文件。如果找到对应的字节码文件,Python会检查文件的时间戳,如果发现字节码文件比模块文件旧(比如修改了源文件),就会重新编译模块文件生成一个新的字节码文档。如果字节码文件不早于相应的.py源代码文件,则跳过源代码到字节码的编译步骤。如果Python在搜索路径上只找到字节码文件,而没有找到源代码,它将直接加载字节码文件(这意味着您可以仅将程序作为字节码文件分发,而避免发送源代码)。也就是说,直接使用字节码文件,跳过编译步骤,会提高程序的启动速度。程序顶层文件的.pyc字节码文件通常是看不到的,除非这个文件也被其他文件导入:只有导入的文件才会在机器上留下一个.pyc。顶层文件的字节码在内部使用后被丢弃,将导入文件的字节码保存在文件中,以提高后续导入的速度。顶级文件通常设计为直接执行,而不是导入。运行导入操作的第一步是执行模块的字节码。文件中的所有语句都会从头到尾依次执行,这一步对变量名的任何赋值操作都会生成模块文件的属性。因此,此执行步骤会生成模块代码定义的所有工具。因为最后的导入步骤实际上是执行文件的程序代码,所以如果模块文件中的任何顶级代码确实起作用,您将在导入它时看到它的结果。3.import语句常见的import语句可以分为两种:单独的import语句用于导入模块名;带有from的import语句用于导入模块中的变量名,您可以使用*导入模块变量中的所有变量。在上面的两条语句中,我们都可以使用as语句为导入的模块或变量分配一个别名。当一个语句包含多个子句(以逗号分隔)时,模块导入的三个步骤将针对每个子句分别执行,就好像这些子句已被分隔成单独的导入语句一样。如果导入的模块被成功获取,它将通过以下三种方式之一绑定到本地命名空间:如果模块名称后跟as,则as之后的变量名将作为对导入模块的引用绑定到本地命名空间对对象的引用。如果没有指定其他名称,并且正在导入的模块是***module(),模块的名称将作为对导入模块对象的引用绑定在本地命名空间中。如果导入的模块不是***模块,则包含该模块的***包的名称将绑定为对本地命名空间中***包的引用。导入的模块必须使用它们的完全限定名称,不能直接访问。包的概念将在后续章节中介绍。from形式稍微复杂一点:找到from子句中指定的模块,必要时加载并初始化;对于import子句中指定的每个标识符:检查导入的模块是否具有该名称的属性b。如果不是,请尝试导入具有该名称的子模块,并再次检查导入的模块的该属性;C。如果未找到该属性,则引发ImportError;d.对的引用存储在本地命名空间中,如果存在则使用as子句中的名称,否则使用属性名称;如果import后面的标识符列表在from语句中被星号(*)替换,则Allpublicnames都绑定在import语句范围的本地名称空间中。(1)import形式import语句将模块导入到文件中:importmodule_nameimport是一个可执行语句,和def一样,是一个隐式赋值语句。Python执行这条语句时,会把import生成的模块对象赋值给import语句后的模块名,而赋值给模块文件顶层任意类型的变量名会作为模块的属性生成目的。导入后,可以通过熟悉的(.)句点属性表示法访问模块的属性(函数和变量)。module.function()module.variableimport语句结合了两个操作;它搜索指定的模块并根据需要执行模块以获得模块对象,然后将模块对象绑定到本地范围内的模块名称。导入语句的搜索操作定义为使用适当的参数调用__import__()函数。直接调用__import__()只是进行一次模块查找,如果找到则进行模块创建操作,并返回模块对象。如果找不到指定的模块,则会引发ImportError。只有import语句执行名称绑定,尽管它们可能伴随着某些其他操作,例如导入父包和更新各种缓存(包括sys.modules)。属性名的点运算在Python中,可以使用点运算语法object.attribute来获取任意对象的attribute属性。点运算符只是一个表达式,它返回与对象匹配的属性名称的值。使用点运算符读取变量名时,显式对象提供给Python,LEGB规则仅适用于没有点运算符的普通变量名。简单变量名X表示在当前范围内搜索变量名X(遵循LEGB规则)点操作X,Y表示在当前范围内搜索X,然后在对象X中搜索属性Y(不在范围)。多点操作X,Y,Z是指在当前范围内搜索X,然后在对象X中搜索属性Y,再在对象X.Y中搜索属性Z。一般性点符号可以用在任何具有属性的对象上:模块、类、C扩展类型等。(2)from-import形式使用from-import语句将模块的属性导入到当前作用域中,并将它们绑定到指定的变量名。frommoduleimportname1[,name2[,...nameN]]和import一样,from-import语句也是一个可执行的隐式赋值语句。import将导入的模块对象分配给模块名称。而from-import将模块中的一个或多个变量(即生成的模块对象的一个??或多个属性)绑定到当前文件中import语句指定的变量名。因为from会将模块中定义的变量名复制到另一个文件的作用域中,它允许我们在另一个文件中直接使用从模块中导入的变量名,而无需传递模块名。(例子:variate)from的第一步也是一个普通的导入操作。因此,from总是将整个模块导入内存(如果尚未导入),无论从该文件中复制了多少变量名。不可能只加载模块文件的一部分(例如,函数)。但由于Python中的模块是字节码而不是机器码,效率问题通常可以忽略不计。from语句的潜在缺陷由于from语句使变量位置更加隐秘和模糊,因此form语句可能会破坏命名空间。如果from用于导入变量,并且这些变量恰好与作用域中的现有变量同名,则变量将被静默覆盖。使用简单的import语句就没有这个问题,因为你必须通过模块名来获取它的属性(变量名)。但是对于from,只要你理解并期望这种情况发生,在实践中这不是一个大问题,特别是如果你显式列出导入的变量名(例如frommoudleimporta,b,c)。from语句在与reload调用一起使用时有一个严重的问题,因为导入的变量名可能引用以前导入的对象。简单的模块通常更喜欢使用import而不是from。大多数from语句用于显式枚举所需的变量,并且每个文件仅限于一次使用from*形式。当您必须使用在两个不同模块中定义的相同变量名时,您确实必须使用import。这种情况下就不能使用from(当然可以在from语句中使用as语句来避免变量名冲突的问题。)。(3)当from-import*形式从一个模块中导入多个变量名时,导入行会越来越长,直到自动换行,我们需要使用反斜杠字符来让一条语句跨越多行。frommoduleimportname1,name2,name3,name4,ame5,name6,name7您可以选择使用多行from-import语句:frommoduleimportname1,name2,name3,name4frommoduleimportname5,name6,name7导入from语句子句时,当我们使用*时,我们会在模块的顶层获得所有已分配变量名的副本。从根本上说,这是获取一个模块的命名空间并将其合并到另一个模块中;同样,最终效果是它允许我们输入更少的代码。from*语句形式只能用在模块文件的顶部,试图在类或函数定义中使用它会引发SyntaxError。核心风格:限制使用“from-import*”在实践中,我们认为“from-import*”不是好的编程风格,因为它“污染”了当前的命名空间,使变量名难以理解。并且很有可能会覆盖当前命名空间中已有的名称,尤其是导入多个模块时。实际上,from*形式将一个名称空间合并到另一个名称空间中,从而破坏了模块的名称空间拆分功能。如果一个模块有许多经常访问的变量或者如果模块有一个长名称,这也很方便。我们只建议在两种情况下使用此方法。一种情况是:要使用的目标模块中的属性太多,不方便重复输入模块名,比如Tkinter(Python/Tk)和NumPy(NumericPython)模块,可能还有socket模块。另一个场合是在交互式解释器中,因为它减少了打字量。通常,我们不鼓励使用已弃用的frommoduleimport*语句。真正的Python程序员应该使用Python的标准分组机制(括号)来创建更合理和明确的多行导入语句。最小化from*破损:_x和__all__将下划线放在变量名前面(例如_x),这可以防止客户端在使用from*语句导入它们时将这些变量名从模块名称中复制出来。这其实是为了尽量减少对命名空间的破坏。下划线不是私有变量的声明:您仍然可以使用其他导入形式查看和修改此类变量名。另外,也可以在模块的顶层为变量名__all__赋值一个变量名的字符串列表,达到类似_x命名约定的隐藏效果。当使用这个特性时,from*语句只会给那些在__all__列表中列出的变量名赋值。实际上,这与x约定相反。__all表示要复制的变量名,_x表示不复制的变量名。Python会首先在模块中查找__all_列表;如果未定义,from*将复制所有不以单个下划线开头的变量名。和_x约定一样,__all__列表只对from*语句的形式有效,不是私有声明。(4)扩展导入语句(as)有时候你导入的模块名或模块属性名已经在你的程序中使用了,或者你不想使用导入的名称,可能输入的东西太长了。这已经成为Python程序员的普遍需求:用你想要的名字替换模块原来的名字。使用扩展的as子句,您可以在导入时指定本地绑定名称。import语句和from语句都可以扩展,允许模块为脚本中的变量赋予不同的名称。importmodulenameasname等同于:importmodulenamename=modulenamedelmodulenamefrommodulenameimportattrnameasname等同于:frommodulenameimportattrnamename=attrnamedelattrname此扩展函数非常常用于替换具有较长变量名的变量以提供较短的同义词,并且当alreadyin当脚本中使用了一个变量名使得普通导入语句的执行将被覆盖时,使用as来避免变量名冲突。4.模块重载在同一个进程中,模块只加载并执行模块在第一次导入时的代码。随后的导入将只使用加载的模块对象,而不会重新加载或重新执行文件的代码。要强制模块重新加载并再次运行,请使用reload()函数。reload()reload()函数位于Python中的imp模块中,使用前必须导入。它强制重新加载并重新执行已加载模块的代码。因为reload()需要对象,所以模块必须在重新加载之前成功导入。重新执行模块文件的代码会覆盖其现有的命名空间。重载会影响所有使用import导入模块的程序,因为使用import的程序需要通过点运算符取出属性。重载后,使用的模块对象变成一个新值。重载只影响在重载后使用from语句导入模块的程序。之前使用from读取属性的客户端不会受到重载的影响,那些程序仍然引用重载之前取出的旧对象。reload()函数可以在不停止整个程序的情况下修改模块程序的某些代码。因此,使用reload(),可以立即看到修改模块的效果。重载不能在所有情况下都使用,但如果使用,它可以缩短开发过程。一般的用法是:导入一个模块,在文本编辑器中修改它的源代码,然后重新加载它。调用reload()时,Python重新读取模块文件的源代码,重新执行其顶级语句。因为Python是解释型的(或多或少),它实际上避免了执行类C程序所需的编译和链接步骤:模块是在程序导入时动态加载的。重载允许您修改正在执行的程序的一部分而不中止它,从而进一步提供性能优势。注意:reload()目前仅适用于用Python编写的模块;用C等语言编写的编译扩展模块在执行过程中也可以动态加载,但不能重新加载。
