python 制作包 / __init__.py / import包的机制探究

这篇博客主要探究__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会所有的命名空间中查找该变量,顺序是:

  1. local namespace 即当前函数或类方法。若找到,则停止搜索;
  2. global namespace 即当前模块。若找到,则停止搜索;
  3. build-in namespace Python会假设变量x是build-in的函数函数或变量。若变量x不是build-in的内置函数或变量,Python将报错NameError。
  4. 对于闭包,若在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章的疑惑。以后自己阅读、修改、编写包也就有了方法。

(原创内容转载请注明出处)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值