Python——模块(2)

44 篇文章 0 订阅
44 篇文章 21 订阅

包导入基础

除了模块名之外,导入也可以指定目录路径。Python代码的目录就称为包,因此,这类导入就称为包导入。
这有点高级的特性,但是它所提供的层次,对于组织大型系统内的文件会很方便,而且可以简化模块搜索路径的设置。

在import语句中列举简单文件名的地方,可以改成列出路径的名称,彼此以点号相隔。

import dir1.dir2.module
from语句也是一样的:

from dir1.dir2.mod import x
这些路径中的“点号”路径是对应于机器上目录层次的路径,通过这个路径可以获得到文件module.py,也就是说,机器上有一个目录dir1,而dir1里有子目录dir2,而dir2内包含了一个名为module.py的模块文件。

此外,这些导入意味着,dir1位于某个容器目录dir0中,这个目录可以在Python模块搜索路径中找到。

----------------------------------------------------------------------------------------------------------------------

__init__.py包文件


如果选择使用包导入,就必须多遵循一条约束:包导入语句的路径中的每个目录内都必须有__init__.py这个文件,否则导入包会失败。也就是说,在上述例子中,dir1和dir2内都必须包含__init__.py这个文件。

__init__.py可以包含Python程序代码,就像普通模块文件。这类文件从某种程度上讲就像是Python的一种声明,尽管如此,也可以完全是空的。更通常的情况下,__init__.py文件扮演了包初始化的钩子,替目录产生模块命名空间以及使用目录导入时实现from *行为的角色。
----------------------------------------------------------------------------------------------------------------------

包导入实例

下面三个文件分别位于目录dir1和dir1的子目录dir2中,这些文件的路径名在注释中给出:

#dir1\__init__.py
print('dir1 init')
x = 1
#dir1\dir2\__init__.py
print('dir2 init')
y = 2
#dir1\dir2\module.py
print('in module.py')
z = 3
这里,dir1要么是我们工作所在的目录的子目录,要么就是位于模块搜索路径中(技术上就是sys.path)的一个目录的子目录。无论哪一种,dir1的容器,即与dir1同级的目录不需要__init__.py文件

当Python向下搜索路径时,import语句会在每个目录首次遍历时,执行该目录的初始化文件。print语句加在这里,用来跟踪它们的执行。此外,就像模块文件一样,任何已导入的目录也可以传递给reload,来强制该项目重新执行。就像这里展示的那样,reload可以接收点号路径名称,来重载嵌套的目录和文件。

>>> import dir1.dir2.module
dir1 init
dir2 init
in module.py
>>>
>>> import dir1.dir2.module
>>> 
>>> from imp import reload
>>> reload(dir1)
dir1 init
<module 'dir1' from 'F:\\data\\dir1\\__init__.py'>
>>> 
>>> reload(dir1.dir2)
dir2 init
<module 'dir1.dir2' from 'F:\\data\\dir1\\dir2\\__init__.py'>
导入后,import语句内的路径会变成脚本的嵌套对象路径。在这里,module是对象,嵌套在对象dir2中,而dir2又嵌套在对象dir1中。
>>> dir1
<module 'dir1' from 'F:\\data\\dir1\\__init__.py'>
>>> dir1.dir2
<module 'dir1.dir2' from 'F:\\data\\dir1\\dir2\\__init__.py'>
>>> dir1.dir2.module
<module 'dir1.dir2.module' from 'F:\\data\\dir1\\dir2\\module.py'>
实际上,路径中的每个目录名都会变成赋值了模块对象的变量,而模块对象的命名空间是由该目录内的__init__.py文件中所有赋值语句进行初始化的。dir1.x引用了变量x,x是dir1\__init__.py中赋值的,而module.z引用的变量z则是在module.py内赋值的。

>>> dir1.x
1
>>> dir1.dir2.y
2
>>> dir1.dir2.module.z
3
----------------------------------------------------------------------------------------------------------------------
包对应的from语句和import语句

import语句和包一起使用时,有些不方便,因为必须在程序中重新输入路径。例如,上例中每次用到z时,就得从dir1开始重新输入完整路径,并且每次都要重新执行整个路径。而直接读取dir2或者module,都会报错

>>> dir2.module
Traceback (most recent call last):
  File "<pyshell#14>", line 1, in <module>
    dir2.module
NameError: name 'dir2' is not defined
>>> module.z
Traceback (most recent call last):
  File "<pyshell#15>", line 1, in <module>
    module.z
NameError: name 'module' is not defined
因此,让包使用from语句,来避免每次读取时都得重新输入路径,这样比较方便。

>>> from dir1.dir2 import module
dir1 init
dir2 init
in module.py
>>> module.z
3
>>> from dir1.dir2.module import z
>>> z
3
>>> import dir1.dir2.module as module
>>> module.z
3
上例中也用到了import的一个扩展功能,as关键字。

========================================================================

包相对导入

截止目前,对包的导入的介绍主要集中在从包的外部导入包文件。在包自身的背部,包文件的导入可以使用和外部导入相同的路径语法,但是,它们也可能使用特殊的保内搜索规则来简化导入语句。也就是说,保内的导入可能相对于包,而不是列出包导入路径。

----------------------------------------------------------------------------------------------------------------------

相对导入基础知识

在Python3.0和Python2.6中,from语句现在可以使用前面的点号(".")来制定,它们需要位于同一包中的模块(所谓的包相对导入),而不是位于模块导入搜索路径上某处的模块(叫做绝对导入)。也就是说:

在Python3.0和Python2.6中,可以使用from语句前面的点号来表示,导入应该相对于外围的包——这样的导入将只是在包的内部搜索,并且不会搜索位于导入搜索路径(sys.path)上某处的同名模块。直接效果就会包模块覆盖了外部的模块。

如下形式的语句:

from . import spam
告诉Python把位于与语句中给出的文件相同包路径中的名为spam的一个模块导入。类似的,语句:

from . spam import name
意味着“从名为spam的模块导入变量name,而这个spam模块与包含这条语句的文件位于同一个包下”


【这部分内容暂时略过,后续需要再补充】
========================================================================

在模块中隐藏数据

Python模块会导出其文件顶层所赋值的所有变量名。没有对某一个变量名进行声明,使其在模块内可见或不可见这种概念。实际上,如果客户想的话,是没有防止客户端修改模块内变量名的办法的

----------------------------------------------------------------------------------------------------------------------

最小化from *的破坏:_X和__all__

有种特定的情况,把下划线放在变量名前面,例如__X,可以防止客户端使用from *语句导入模块名时,把其中的那些变量名复制出去。这其实是为了对命名空间的破坏最小化而已。而from *会把所有的变量名复制出去,导入者可能得到超出它所需的部分(包括会覆盖导入者内的变量名的变量名)。下划线不是“私有”声明:你还是可以使用其他导入形式看见并修改这类变量名,比如使用import语句。
此外,也可以在模块顶层把变量名的字符串列表赋值给变量__all__,以达到类似于_X命名惯例的隐藏效果。例如

__all__ = ['Error','encode','decode']
使用此功能时,from *语句只会把列在__all__列表中的这些变量名复制出来。这和_X惯例相反:__all__是指出要复制的变量名,而_X是指出不要被复制的变量名。
Python会先寻找模块内的__all__列表,如果没有定义的话,from * 就会复制出开头没有单下划线的所有变量名。
================================================================= =======
混合用法模式:__name__和__main__

这是一个特殊的与模块相关的技巧,可把文件作为模块导入,并以独立式程序的形式运行。每个模块都有个名为__name__的内置属性,Python会自动设置该属性:

1.如果文件是以顶层程序文件执行,在启动时,__name__就会设置为字符串"__main__"
2.如果模块文件被导入,__name__就会改设称客户端所了解的模块名

结果就是模块可以检测自己的__name__,来确定它是在执行还是在导入。例如,假设我们建立下面的模块文件,名为runme.py,它只导出了一个名为tester的函数。

def tester():
    print("It's Christmas in Heaven...")

if __name__ =='__main__':
    tester()
这个模块定义了一个函数,让用户可以正常地导入并使用:

>>> import runme
>>> runme.tester()
It's Christmas in Heaven...
这时候可以看到该模块的__name__为:

>>> runme.__name__
'runme'
然而这个模块也在末尾包含了当此文件以程序执行时,就会调用该函数的代码,可以直接运行:
>>> ================================ RESTART ================================
>>> 
It's Christmas in Heaven...
实际上,一个模块的__name__变量充当一个使用模式标志,允许它编写成一个可导入的库和一个顶层脚本。尽管简单,我们会看到这一钩子几乎在可能遇到的每个Python程序文件中应用。也许使用__name__最常见的就是自我测试代码。
----------------------------------------------------------------------------------------------------------------------

以__name__进行单元测试

在讨论参数的时候,编写过一个脚本,从一组传进来的参数中计算出其最大和最小值。

def minmax(test,*args):
    res = args[0]
    for arg in args[1:]:
        if test(arg,res):
            res = arg
    return res

def lessthan(x,y):return x<y
def grtrthan(x,y):return x>y

print(minmax(lessthan,4,2,1,5,6,3))
print(minmax(grtrthan,4,2,1,5,6,3))
这个脚本在末端包含了自我测试程序代码。所以不用每次执行时,都得在交互模式命令行中重新输入所有代码就可以进行测试。然而,这种写法的问题在于,每次这个文件被另一个文件作为工具导入的时候,都会出现调用自我测试所得到的输出:这可不是用户友好的特性。改进之后,我们在__name__检查区块内封装了自我测试的调用,使其在文件作为顶层脚本执行时才会启动,而导入时不会。
print('I am:',__name__)

def minmax(test,*args):
    res = args[0]
    for arg in args[1:]:
        if test(arg,res):
            res = arg
    return res

def lessthan(x,y):return x<y
def grtrthan(x,y):return x>y

if __name__ == '__main__':
    print(minmax(lessthan,4,2,1,5,6,3))
    print(minmax(grtrthan,4,2,1,5,6,3))
我们也在顶端打印__name__的值,目的是来跟踪它的值。
当我们执行它的时候:

>>> 
I am: __main__
1
6
但是,如果我们导入这个文件,其名称不是__main__,就必须明确地调用这个函数来执行。
>>> import min
I am: min
>>> min.minmax(lessthan,4,2,1,5,6,3)
1
同样的,不管是否用于测试,结果都是让代码有两种不同的角色:作为工具的库模块,或者是作为可执行的程序。
================================================================= =======

修改模块搜索路径

前面已经介绍过,模块搜索路径是一个目录列表,可以通过环境变量PYTHONPATH以及可能的.pth路径文件进行定制。这里要说的是,Python程序本身也可以修改搜索路径,也就是修改sys.path的内置列表。
sys.path在程序启动时就会进行初始化,但在那之后,可以随意对其元素进行删除,附加和重设。

>>> import sys
>>> sys.path
['F:/data/PythonTest', 'C:\\Python34\\Lib\\idlelib', 'F:\\data\\PythonTest', 'C:\\Windows\\SYSTEM32\\python34.zip', 'C:\\Python34\\DLLs', 'C:\\Python34\\lib', 'C:\\Python34', 'C:\\Python34\\lib\\site-packages']
>>> sys.path.append('C:\\sourcedir')
>>> sys.path
['F:/data/PythonTest', 'C:\\Python34\\Lib\\idlelib', 'F:\\data\\PythonTest', 'C:\\Windows\\SYSTEM32\\python34.zip', 'C:\\Python34\\DLLs', 'C:\\Python34\\lib', 'C:\\Python34', 'C:\\Python34\\lib\\site-packages', 'C:\\sourcedir']
一旦做了这样的修改,就会对Python程序中将要导入的地方产生影响,因为所有导入和文件都共享了同一个sys.path列表。事实上,这个列表可以任意修改。

========================================================================

import语句和from语句的as扩展

import modulename as name
相当于:
import modulename
name = modulename
del modulename              #don't keep original name
在这类import之后,必须使用列在as之后的变量名来引用该模块。from语句也可以这么用,把从某个文件导入的变量名,赋值给脚本中的不同的变量名:
from modulenane import attrname as name
这个扩展功能很常用,替变量名较长的变量提供简短一些的同义词,而且当已经在脚本中使用一个变量名使得执行普通import语句会被覆盖时,使用as就可避免变量名冲突。

import reallylongmodulename as name
name.func()

from module1 import utility as util1
from module2 import utility as util2
此外,使用之前说过的包导入时,也可为整个目录路径提供简短、简单的名称,十分方便。
import dir1.dir2.module as module
module.func()

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值