模块和包

 

import 导入机制

  

一、标准 import

  Python中所有加载到内存的模块都存放在 sys.modules 列表中。

  当import xxx 一个模块时,首先去 sys.modules 列表中查找是否已经加载了 xxx模块,

    如果已经加载了,就把模块名字xxx 加入到正在调用 import xxx 模块的 Local 名字空间中。

    如果没有加载,则从 sys.path 目录中按照模块名称查找模块文件,模块可以是py、pyc、pyd,找到后将模块载入内存,并加到 sys.modules 中,并将名称导入到当前的 Local 名字空间。

  一个模块不会重复导入,多个不同的模块都可以用 import 引入同一个模块到自己的 Local 名字空间中。

  import 只能导入模块,不能导入模块中的 对象(函数、类、变量等);

    例如:模块 A 中 有一个函数 getName(),另一个模块不能通过 import A.getName 将 getName 导入到本模块,只能用 from A import getName。

 

二、嵌套 import

  (1)顺序嵌套。

  本模块导入A模块(import A),A中又 import B,B模块又 import 其他模块 ....

  本模块 import A 后,本模块只能访问A,不能访问 模块B 以及 其他模块,即使 B模块已经加载在内存中,如果访问还要在本模块中 import B。

  (2)循环嵌套。

  文件[ A.py ]

    from B import D

    class C: pass

  文件[ B.py ]

    from A import C

    class D: pass

  当执行文件 A.py 到 import D时会报错,但是将文件[ A.py ],修改为 import B 即可。

  当执行语句 from B import D 时,分为以下步骤:

      1)在 sys.modules 中查找符号 “B”

      2)如果符号 “B” 存在,则直接获得 符号 “B” 对应的 module 对象。

      从 <module B> 对象的 __dict__ 中获得符号 “D” 对应的对象,如果 “D” 不存在,则抛出异常。

      3)如果符号 “B” 不存在,则创建一个新的 module 对象, <module B> ,此时 <module B> 的 __dict__ 为空,

      执行[ B.py ] 中的表达式,填充  <module B> 的 __dict__ ,

      从 <module B> 对象的 __dict__ 中获得符号 “D” 对应的对象,如果 “D” 不存在,则抛出异常。

  例子中的执行顺序为:

  (1)先执行 A.py 中的 from B import D,由于是执行的 python A.py ,所以 sys.modules 中并没有 <module B>,首先为 B.py 创建一个module对象( <module B> ),

      此时,module 对象是空的,然后解析执行 B.py。

  (2)转去执行 B.py 中的 from A import C,首先检查 sys.modules 是否存在 <module A>,由于此时还没有缓存 <module A> ,所以类似的,Python内部为 A.py 创建一个 module 对象( <module A>),然后同样转去执行 A.py中的语句。

  (3)再次执行 A.py 中的 from B import D 时,由于在第(1)步创建的 <module  B> 对象,已经缓存在 sys.modules 中,所以直接就得到了 <module B>,但是<module B>,依旧是一个空对象,当从<module B> 获取 符号 “D” 时,就会抛出异常。如果这里是 import B,由于符号 “B” 在 sys.modules 中已经存在,所以不会抛出异常。

 

三、包 import

  只要一个文件夹下面有 __init__.py 文件,这个文件夹就可以看做包。

  包的导入过程和模块基本一致,只是导入包的过程中,先执行包目录下的 __init__.py。

  如果只是单纯的导入包,而包的 __init__.py 中没有其他初始化的操作,那么此包下面的模块是不会自动导入的。

 

 

一、把模块按层次结构组织成包

  创建一个软件包在目录中定义一个__init__.py文件。

  __init__.py文件的目的就是包含可选的初始化代码,当遇到软件包中不同层次的模块时会触发运行。

  对于导入语句:>>> import graphics.formats.jpg

  文件graphics/__init__.py 和 graphics/formats/__init__.py 都会在导入文件graphics/formats/jpg.py之前优先得到导入!

  大部分情况下,把__init__.py文件留空也是可以的。但是,在某些特定的情况下__init__.py文件可用来加载子模块。

# graphics/formats/__init__.py

from . import jpg
from . import png

 

二、对所有符号的导入进行控制

  使用>>> from module import * 语句时,在模块中定义__all__,那么只有显式列出的符号名才会被导出。

  如果将__all__定义成一个空列表,那么任何符号都不会被导出。

  如果__all__包含有未定义的名称,那么在执行import语句时会产生一个AttributeError异常。

  __all__ = [ 'spam', 'grok' ]

 

三、用相对名称来导入包中的子模块

  要在软件包的子模块中导入同一个包中其他的子模块,请使用相对名称来导入。

mypackage/
    __init__.py
    A/
        __init__.py
        spam.py
        grok.py
    B/
        __init__.py
        bar.py

  如果模块mypackage.A.spam要导入位于同一个目录中的模块grok,应该使用 

  >>> from . import grok

  如果模块mypackage.A.spam要导入位于不同目录中的模块 B.bar,应该使用 

  >>> from ..B import bar

  >>> from mypacksge.A import grok

  使用绝对名称的缺点在于这么做会将最顶层的包名称硬编码到源代码中,这使得代码更加脆弱,如果想重新组织一下结构会比较困难。

  ▲ 一切包的相对导入都是在主程序所在目录之下进行的,不能导入它的上一级目录中的包。

  ▲ 位于脚本顶层的模块不能使用相对导入。

  ▲ 包的某个部分是直接以脚本的形式执行的,这种情况下也不能使用相对导入。

 

四、将模块分解成多个文件

  可以通过将模块转换为包的方式将模块分解成多个单独的文件。

# mymodule.py

class A:
    def spam(self):
        print('A.spam')

class B(A):
    def bar(self):
        print('B.bar')

  把mymodule.py分解为两个文件,每个文件包含一个类的定义。

mymodule/
    __init__.py
    a.py
    b.py

# a.py
class A:
    def spam(self):
        print('A.spam')

# b.py
from .a import A
class B(A):
    def bar(self):
        print('B.bar')

# __init__.py
from .a import A
from .b import B

  创建一个包目录,并通过__init__.py文件将各个部分粘合在一起。

  引入惰性导入的概念。__init__.py文件一次性将所有需要的组件都导入进来。

  如果只希望在实际需要的时候才加载那些组件。为了实现这个目的,下面对__init__.py文件做了修改:

# __init__.py
def A():
    from .a import A
    return A()

def B():
    from .b import B
    return B()

  惰性加载的主要缺点在于会破坏继承和类型检查机制。

  >>> if isinstance(x, mymodule.A):   # Error

  >>> if isinstance(x, mymodule.a.A):  # OK

 

五、让各个目录下的代码在统一的命名空间下导入

  使各个单独的目录统一在一个公共的命名空间下,可以把代码像普通的Python包那样进行组织。

  但是对于打算合并在一起的组件,这些目录中的__init__.py文件则需要忽略。

foo-package/
    spam/
        blah.py

bar-package/
    spam/
        grok.py

  spam同来作为公共的命名空间。注意这两个目录中都没有出现__init__.py文件。

  将foo-package和bar-package都添加到Python的模块查询路径中,然后尝试做一些导入操作。

  >>> sys.path.extend(['foo-package', 'bar-package'])

  >>> import spam.blah

  >>> import spam.grok

  两个不同的包目录合并在了一起,可以随意导入spam.blah或者spam.grok。

原理:

  使用了命名空间包的特性。

  命名空间包是一种特殊的包,用来合并不同目录下的代码,把他们放在统一的命名空间之下进行管理。

  关键在于确保统一命名空间的顶层目录不包含__init__.py文件。 

  命名空间包的一个重要特性是任何人都可以用自己的代码来扩展命名空间中的内容。

  每个包都在__file__模块中保存了全路径。

  如果缺少__file__属性,这个包就是命名空间。

 

六、重新加载模块

  重新加载一个已经加载过了的模块,使用imp.reload()

  >>> import spam

  >>> import imp

  >>> imp.reload(spam)

  但是,reload()操作不会更新 from module import xxx 这样的语句导入的定义。

 

七、让目录或zip文件称为可运行的脚本

  创建一个目录或zip文件,在其中添加一个__main__.py,这是一种打包规模较大的Python应用程序的可行方法。

 

   

八、读取包中的数据文件

  包的结构:

mypackage/
    __init__.py
    somedata.dat
    spam.py

  >>> import pkgutil

  >>> data = pkgutil.get_data(__package__, 'somedata.dat')

  get_data()的第一个参数是包含有包名的字符串。我们可以直接提供这个字符串,或者使用__package__这个特殊变量。

  第二个参数是要获取的文件相对于包的名称。

 

九、添加目录到sys.path中

  创建一个.pth文件,将目录写出来:

# myapplication.pth
/some/dir
/other/dir

  这个.pth文件需要放在Python的其中一个site-packages目录中,一般来说位于/usr/local/lib/python3.3/site-packages。

  在解释器启动的时候,只要.pth文件中列出的目录存在于文件系统中,那么它们就会被添加到sys.path中。

 

十、使用字符串中给定的名称来导入模块

  当模块或包的名称以字符串的形式给出时,可以使用importlib.import_module()函数来手动导入这个模块。

  >>> import importlib

  >>> math = importlib.import_module('math')

  >>> math.sin(2)

  导入包 from . import b 的用法:

  >>> b = importlib.import_module('.b', __package__)

 

 

十三、安装只为自己所用的包

  Python有一个用户级的安装目录,通常位于~./local/lib/python3.3/site-packages这样的目录下。

  python3 setup.py install -user  或者

  pip install --user packagename

  用户级的site-package目录通常会在sys.path中出现,而且位于系统级的site-package目录之前。

  因此,采用这种技术安装的包比已经安装到系统中的包优先级要高

 

十四、创建新的Python环境

  通过pyvenv命令创建一个新的虚拟环境。

  bash % pyvenv Spam

  Spam目录下会创建出 bin、include、lib、pyvene.cfg目录,并且在bin目录下有一个可使用的Python解释器。

  bash % Spam/bin/python3

  创建虚拟环境大部分原因都是为了安装和管理第三方的包。

  下一步通常是安装一个包管理器,distribute或者pip

  如果想将已经安装过的包引入,使其作为虚拟环境的一部分,那么可以使用--system-site-packages来创建虚拟环境。

  bash % pyvenv --system-site-packages Spam

 

十五、发布自定义的包

  把编写的库分发给其他人使用。

  典型的程序库结构:

projectname/
    README.txt
    Doc/
        documentation.txt
    projectname/
        __init__.py
        foo.py
        bar.py
        utils/
            __init__.py
            spam.py
            grok.py
    examples/
        hellworld.py

  首先编写一个setup.py文件。

from distutils.core import setup

setup(name='projectname',
  varsion='1.0',
  author='Your Name',
  author_email='you@youaddress.com',
  url='http://www.you.com/projectname',
  packages=['projectname', 'projectname.utils']               
)

  需要手动列出包中的每一个子目录:packages=['projectname', 'projectname.utils']

  接下来创建一个MANIFEST.in文件。列出各种希望包含在包中的非源代码文件:

# MANIFEST.in
include *.txt
recursive-include examples *
recursive-include Doc *

  确保setup.py和MANIFEST.in文件位于包的顶层目录。

  % bash python3 setup.py sdist

  系统会创建出projectname-1.0.zip或者projectname-1.0.tar.gz这样的文件。

 

posted @ 2019-09-25 23:51  5_FireFly  阅读(304)  评论(0编辑  收藏  举报
web
counter