这篇博客主要探究__init__.py文件在多层结构包中的作用,目的是自己制作包,以及阅读第三方包的源码。
1.基本概念:
这里引用了CSDN-Python 3.x | 史上最详解的 导入(import)的内容:
1.1模块:
模块 module:一般情况下,是一个以.py为后缀的文件。
module来源有3种:
①Python内置的模块(标准库)
②第三方模块
③自定义模块
1.2包 package:
当一个文件夹下有 init .py时,该文件夹是一个包(package)
1.3命名空间
- 每个函数function 有自己的命名空间,称local namespace,记录函数的变量。
- 每个模块module 有自己的命名空间,称global namespace,记录模块的变量,包括functions、classes、导入的modules、module级别的变量和常量。
- build-in命名空间,它包含build-in function和exceptions,可被任意模块访问。
某段Python代码访问 变量x 时,Python会所有的命名空间中查找该变量,顺序是:
- local namespace 即当前函数或类方法。若找到,则停止搜索;
- global namespace 即当前模块。若找到,则停止搜索;
- build-in namespace Python会假设变量x是build-in的函数函数或变量。若变量x不是build-in的内置函数或变量,Python将报错NameError。
- 对于闭包,若在local namespace找不到该变量,则下一个查找目标是父函数的local namespace。
2.引用包/模块
2.1.引用第三方模块(这个应该大家都会)
import pandas
pandas.DataFrame()
上述代码通过import xxx 导入 xxx包
2.2.引用第三方包中的模块
这次我们制作一个稍微复杂一点的myPandas包(这里包的结构先抛开__init__.py不谈)
文件结构:
test
|-test.py
|-myPandas
|-core
|-frame.py
myPandas就是一个包,里面有一个core子包,core里有一个frame模块
文件内容:
frame.py
class DataFrame:
def __init__(self):
print('this is DataFream')
test.py
import myPandas.core.frame
myPandas.core.frame.DataFrame()
运行test.py输出结果:
this is DataFream
2.3 总结
导入模块提供的函数/类:import 模块名
导入包中模块里提供的函数/类:import 包名.子包名.模块名
3.导包中的问题
现在让我们回想一下使用pandas的经历:
既然能使用pandas.DataFrame(),那么参考2.1,猜测pandas库应该是有个pandas.py,里面定义了DataFrame类吧
很遗憾事实不是这样,pandas是个结构复杂庞大的库,在github上他的源码看起来是这样的
Dataframe类其实定义在pandas包下的core子包下的frame.py模块里
那么按照2.2的实验,应该需要用import pandas.core.frame导入模块,然后才能使用pandas.core.frame.DataFrame()
为啥子可以pandas.DataFrame()这样直接用了啊!我承认这样很方便,但这样用不会有问题吗!举个例子,如果pandas的作者为win和linux两个平台编写各自的代码,文件结构如下
pandas
|core
|-frame_win.py -> class DataFrame
|-frame_linux.py -> class DataFrame
那么使用pandas.DataFrame()就不知道调用的是哪个了啊
要解释这一切,就要回到刚刚被我们抛在一边不谈的__init__.py
4.__init__.py
参考CSDN-Python __init__.py 文件使用,这个文件主要有3个作用:
1. Python中package的标识,不能删除
2. 定义__all__用来模糊导入
3. 编写Python代码
前两个作用比较简单,没什么异议,第三个功能可以编写Python代码可玩性就很高了。当然,为了保持简洁__init__.py里不应该放具体实现代码,那应该写啥呢,我们打开第三方库会发现这些__init__.py文件里经常会写import语句。让我们看看这些import是如何实现骚操作的
我们首先需要知道导入包与调用包的机制:
导包:
使用 import frame.py 时,python会创建<module frame>对象,然后执行一遍 frame.py 以获取frame模块的global命名空间,用来填充<module frame>对象的__dict__(也就是frame的属性)。
如果 import pandas包 会有啥不一样呢?python还是会创建一个叫pandas的module对象,不过这次会执行包下的__init__.py,以填充pandas的属性。
调用包:
当我们运行 pandas.core.frame.DataFrame()代码时,发生了两件事,首先在命名空间找到pandas;然后通过"."这个属性运算符找到pandas的属性core,再找到pandas.core的属性frame...
也就是说,__init__.py决定了向包的名字空间暴露啥内容,而包的名字空间就是这个包的属性:
(A picture is worth a thousand words.)
在包packA的__init__.py文件决定了暴露什么到__init__.py的名字空间以及如何暴露(import as xxx),test.py里 import packA 的时候__init__.py名字空间里的内容就被映射为packA这个module对象的属性了。
再拓展一下,来个多层的结构
OKOK,到这里我们已经基本解决了第3章的疑惑。以后自己阅读、修改、编写包也就有了方法。
(原创内容转载请注明出处)