导包写包看这个就够啦!!Python的模块、包、库

导包写包看这个就够啦!!Python的模块、包、库

也仅仅是我的一些思考整理,可能会更新,欢迎指正!
[email protected]

在开发的时候,我们很容易使用一些别人的轮子来加快我们的开发进度,或者自己造一些轮子来更好的集成一些功能,这种轮子我们一般称之为第三方库或者说第三方包

简单的来讲,例如我们使用import datetime就能够导入名为datetime的包(package)

# 导入package datetime
import datetime
# 获取时间
print(datetime.datetime.ctime())

或者可以使用 from datetime import datetime

# 导入package datetime
from datetime import datetime
# 获取时间
print(datetime.ctime())

如果仅仅使用,似乎也没有什么问题,但是大家有思考过import进去的到底是什么么?package datetime到底是一个文件夹?还是一个.py文件?大家有没有碰到import进不去,只能from xxx import的情况? 自己写的包却提是Module不存在?

一、package和Module

1.1 什么时package和Module

我们一般说第三方库都不太在一package和Module的区别,事实上两者的关系非常的自然。

当我们想实现某个功能时,我们创建.py文件来书写代码,这里就是我们的主程序。

当我们功能实现比较复杂,代码重复过多,我们就会把重复代码打包,称之为函数。或者有面向对象的思维的话,可能会写一个类。

当我们这个类或者函数可以被很多地方用到时,我们将这些函数和类统一整理到一个.py文件中,这样我们就能使用import来导入这个文件,使用其中定义好的函数和类。这里这个.py文件就是一个Module。一般简单的Module就是包含一个类,类中定义了变量和操纵类的函数。这里也就是简单的一个轮子了。

但是复杂的功能实现可能会利用到非常多的类,例如flask,tensorflow等框架,会非常的复杂。为了方便导入,开发者就会把这些文件整理成package。

**总结:**Module就是一个.py文件而package就是一个包含多个Module的包

1.2 package的特点

Module简单的理解为定义了一个或者多个class的.py文件就可以,那package有什么特点呢?

package和dir文件夹非常像,图标差别不大,就是多了一个小圈圈,最重要的是,多了一个__init__.py文件。很多博客里面也讲,多了__init__.py文件就是包了。这么讲也没错,但是没有讲清楚__init__.py文件是非常重要的。

二、import

2.1 import可以导入什么?

import可以直接导入:

  • package
  • Module
  • Module中定义的类或函数

事实上import导入的就是模块,只不过很多我们看着不像是模块,例如package,函数等,都可以理解成模块。

2.2 import时发生了什么

系统在导入模块时,要做以下三件事:

  • 为源代码文件中定义的对象创建一个名字空间,通过这个名字空间可以访问到模块中定义的函数及变量。
  • 在新创建的名字空间里执行源代码文件。
  • 创建一个名为源代码文件的对象,该对象引用模块的名字空间,这样就可以通过这个对象访问模块中的函数及变量

import的本质就是路径搜索。告知程序我引入了那些Module,在编译时会根据路径对相应的程序进行编译。使用高级IDE利用Pycharm等会对变量,导入等进行校验。

import numpy as np
if __name__ == "__main__":
    print(np.__name__)
'''
输出:
	numpy
'''

上面的小demo可以看到我们导入了numpy这个package。numpy是非常大的一个包,文件结构如下图所示。

在这里插入图片描述

关键字 as 是将numpy命名为np方便在编程时使用,但是调用np.__name__发现输出仍然为numpy,可见as仅仅是改变了numpy在本程序中指代的变量的名字,而np就是一个储存着numpy路径的变量,也就是一个引用。

2.3 为什么要使用from xxx import

之前讲到,import本质是路径搜索,搜索时候的顺序是怎么样的呢?

  • 模块导入时,默认先在当前目录下查找,然后再在系统路径中查找

系统路径查找的范围:sys.path

import sys
if __name__ == "__main__":
    # 打印系统路径
    print(sys.path)
    # 添加系统路径
    sys.path.append("书写绝对路径")

将写的package路径添加到系统路径中也是解决自己写的模块/包找不到的一个方法。

from的功能/优点

  • 使用from语句可以将模块中的对象直接导入到当前的名字空间. from语句不创建一个到模块名字空间的引用对象,而是把被导入模块的一个或多个对象直接放入当前的名字空间.
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis

​ 这样导入的仅仅是Module(.py文件)中的一部分内容,而不是全部。

​ 在需要重复导入某个很大的包时,可以只导入一部分,减少开销。

  • 外一个from的功能就是寻找文件,import可能并不能搜索到你需要的包,但是可能你需要的包就在当前目录下的一个子文件夹内,你就可以方便的进行引用,例如
from demo.demoPack.demoModule import hanshu

'''
|demo
	|--__init__.py
	|--test.py
    |--demoPack
        |--__init__.py
        |--demoModule.py
demoModule.py
    def hanshu():
        print("hanshu")
        return "yes"

    class Printer:
        def __init__(self):
            print("init printer")
'''

三、Module参数

4.1 __all__

可以使用__all__=[‘名称1’,‘名称2’]来自定义 import * 可以导入的对象

4.2 __name__

每个模块都拥有__name__属性,它是一个内容为模块名字的字符串。最顶层的模块名称为__main__。命令行或是交互模式下程序都运行在__main__ 模块内部。

相信很多人都看过这样的代码

def main():
	return 0
if __name__ == '__main__':
	main()

这里的if __name__ == ‘__main__’:可以简单的理解为程序的入口。也就是当这个程序作为主程序时,.py文件作为主程序,那么其参数__name__ 就是 ‘__main__’。当作为第三方包导入时,__name__ 就是.py的全限定类名。这样的话就可以利用__name__来进行一些操作,例如:2.3中的例子。

'''
|demo
	|--__init__.py
	|--test.py
    |--demoPack
        |--__init__.py
        |--demoModule.py
demoModule.py
    def hanshu():
    print("hanshu")
    return "yes"

class Printer:
    def __init__(self):
        print("init printer")
    def test(self):
        print("run Printer.test()")
        print(__name__)


if __name__ == 'demo.demoPack.demoModule':
    print("import as Module...")
'''
# test.py
from demo.demoPack.demoModule import Printer
if __name__ == "__main__":
    print("创建对象")
    p = Printer()
    print("调用方法")
    p.test()

'''
输出:
import as Module...
创建对象
init printer
调用方法
run Printer.test()
demo.demoPack.demoModule
'''

四、__init__.py文件的作用

4.1 将文件夹变为一个package

前面我们说了,package和文件夹的一个重要区别就是当前路径下是否存在__init__.py文件。__init__.py文件通常为空文件,存在只是给编译器的一个提醒。

注意:

__init__.py虽然常为空,但是在导入package时,实际上时运行了__init__.py文件。这里可以将整个package看作是一个class,而导入是调用了(或者说是创建,但是都不是正确的,只是可以这么理解)这个class。class就会调用def __init__(self): 方法来初始化自己。

4.2 控制package的导入

和Module一样,使用

# __init__.py
__all__ = ['os', 'sys', 're', 'urllib']

# a.py
from package import *

可以控制package中导入*时,可以导入的模块

4.3 提前导入package

package可以时多级的,如果你想导入的模块在非常深,但是你只能看见很外层的package,那你只能用from一层一层的进入更深的package然后导入。但是我们可以在当前package的__init__.py中提前导入一些包,这样当你from当前的package,你可以直接import非常深的模块(前提是你已经在__init__.py中导入过,还记得导入是运行__init__.py么?)

4.4 固定全局参数(这一点也是我写这篇文章的主要的原因。)

在使用logging包的时候,我发现只需要在主程序(__main__)中配置一次logging(例如创建多个不同的logger并设置不同的level和输出方式等),在调用自己写的模块时,只要调用的模块import logging,我就可以直接使用主程序创建的logger。总的就是,logging实现了全局的配置,就算再多个不同的.py文件中。

刚好我也需要自己创建一个可以在一个大项目中全局存在的唯一对象(java的单例模式)。

提到全局存在,调用一个,除了java的单例模式,其实很容易想到类中的静态变量,但是创建的对象如何在多个文件中同时存在的问题还是没有解决,知道我看到了这么一段话
在这里插入图片描述
其实表达的意思是,对应.py文件。但是我突然想到是不是整个package可以看成是一个对象呢(这也是我之前用类中def __init__(self)方法类比__init__.py文件的原因)。

找到了logging的源文件,logging的主要程序都写在了__init__.py文件中,所以使用的时候import logging其实是导入了package而不是通常我们导入的Module。
在这里插入图片描述
__init__.py文件中,定义了

root就是默认存在的logger,level是WARNING,管理多个logger使用了Manager这个提前创建好的对象。

也就是直接__init__.py文件中的变量/对象,你不论怎么在哪里import package都是相同的,改变也是全局的

**注意:**这里全局的意思是,如果你使用框架,而框架中实现logger的方式是基于logging的(例如flask就是对logging进行打包封装到app对象中),那么你在其他程序中对logging的配置,例如logging.basicConfig(),很有可能会影响到flask中的配置。

五、参考

Python Import 详解

Python中import机制

Python init.py 作用详解

python-模块,包,安装

猜你喜欢

转载自blog.csdn.net/qq_39117858/article/details/107143305