python导入和模块化

知识共享许可协议 版权声明:署名,允许他人基于本文进行创作,且必须基于与原先许可协议相同的许可协议分发本文 (Creative Commons

1. 导入

import 语句:
  1. 找到指定的模块,加载和初始化,生成模块对象,找不到,抛出异常
  2. 在import所在的作用域的局部命名空间中,增加名称和上一步创建的兑现关联
# import os.path  # 注意os模块和path模块都加载了,但是dir()中只能拿到os
import os  # import后只能写模块名
import os.path
import os.path as osp  # 取别名,注意是os.path的别名,不是os的别名

print(dir())
# ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__',
# '__package__', '__spec__', 'os']
# print(sorted(locals().keys()))
# print(sorted(globals().keys()))

print(os)
print(os.path)
print(osp)
print(os.path.exists('c:/tt'))  # 虽然没有导入path模块但是解释器替你做了,所以此语句可以执行
print(osp.exists('c:/text.txt'))

def a():
    import pathlib
    print(dir())  # ['pathlib'] 受作用域的影响


a()
print('~~~~~~~~~~')
print(dir())
# ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__',
# '__package__', '__spec__', 'a']

  总结:导入顶级模块,其名称会加入到本地名词空间中,并绑定到其模块对象;导入非顶级模块,只将其顶级模块名称加入到本地名词空间中。导入模块必须使用完全限定名称来访问。如果使用了as,as后的名称直接绑定到导入的模块对象,并将该名称加入带本地名词空间中。

from … import语句
from os import stat  # from后必须是模块,可以逗号分隔
from os.path import exists
from pathlib import Path
from pathlib import *
from functools import _make_key
# from os import path as osp
from functools import wraps as wr, update_wrapper

# from module import * | module | class | function | ...
print(dir())

from os.path import exists  # 注意from后的模块并不导入,只是加载
import os.path


print(exists)

print(os.path.exists)
print(os.path.__dict__['exists'])
print(getattr(os.path, 'exists'))
# <function exists at 0x0000000001DE31E0>
总结:
  • 找到from子句中指定的模块,加载并初始化它(注意不是导入)
  • 对于import子句后的名称:
        1.先查from子句导入的模块是否具有该名称的属性
        2.如果不是,则尝试导入该名称的子模块
        3.还没有找到,则抛出ImportError异常
        4.这个名称保存到本地名词空间中,如果有as子句,则使用as子句后的名称
from pathlib import Path
import pathlib as pl


print(id(Path), Path)
# 42604072 <class 'pathlib.Path'>
print(id(pl.Path), pl.Path)
# 42604072 <class 'pathlib.Path'>
p1 = Path()
p2 = pl.Path()  # 注意p1和p2肯定不一样
print(Path is pl.Path)  # True

2.自定义模块

自定义模块的命名规范:

  1. 模块名就是文件名
  2. 模块名必须符合标识符的要求,是非数字开头的字母、数字和下划线的组合,也不要使用中文。
  3. 不要使用系统模块名来避免冲突,除非你明确知道这个模块名的用途。
  4. 通常模块名全为小写,下划线来分割。

3.模块搜索顺序

使用sys.path查看搜索顺序:

import pathlib
import sys


print(*sys.path, sep='\n')
print(pathlib.Path.__doc__)
print(sys.path)
"""C:\Users\Administrator\PycharmProjects\面向对象进阶\venv\Scripts\python.exe C:/Users/Administrator/PycharmProjects/面向对象进阶/模块化/导入.py
C:\Users\Administrator\PycharmProjects\面向对象进阶\模块化
C:\Users\Administrator\PycharmProjects\面向对象进阶
C:\Users\Administrator\PycharmProjects\面向对象进阶\venv\Scripts\python36.zip
C:\Users\Administrator\AppData\Local\Programs\Python\Python36\DLLs
C:\Users\Administrator\AppData\Local\Programs\Python\Python36\lib
C:\Users\Administrator\AppData\Local\Programs\Python\Python36
C:\Users\Administrator\PycharmProjects\面向对象进阶\venv
C:\Users\Administrator\PycharmProjects\面向对象进阶\venv\lib\site-packages
C:\Users\Administrator\PycharmProjects\面向对象进阶\venv\lib\site-packages\setuptools-39.1.0-py3.6.egg
C:\Users\Administrator\PycharmProjects\面向对象进阶\venv\lib\site-packages\pip-10.0.1-py3.6.egg
PurePath subclass that can make system calls.

    Path represents a filesystem path but unlike PurePath, also offers
    methods to do system calls on path objects. Depending on your system,
    instantiating a Path will return either a PosixPath or a WindowsPath
    object. You can also instantiate a PosixPath or WindowsPath directly,
    but cannot instantiate a WindowsPath on a POSIX system or vice versa.
    
['C:\\Users\\Administrator\\PycharmProjects\\面向对象进阶\\模块化', 'C:\\Users\\Administrator\\PycharmProjects\\面向对象进阶', 'C:\\Users\\Administrator\\PycharmProjects\\面向对象进阶\\venv\\Scripts\\python36.zip', 'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python36\\DLLs', 'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python36\\lib', 'C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python36', 'C:\\Users\\Administrator\\PycharmProjects\\面向对象进阶\\venv', 'C:\\Users\\Administrator\\PycharmProjects\\面向对象进阶\\venv\\lib\\site-packages', 'C:\\Users\\Administrator\\PycharmProjects\\面向对象进阶\\venv\\lib\\site-packages\\setuptools-39.1.0-py3.6.egg', 'C:\\Users\\Administrator\\PycharmProjects\\面向对象进阶\\venv\\lib\\site-packages\\pip-10.0.1-py3.6.egg']

"""

  当加载一个模块的时候,需要从这些搜索路径中从前往后依次查找,并不搜索这些目录的子目录。搜索到模块就加载,搜索不到就抛异常。路径也可以是 字典、zip文件、egg文件。.egg文件,由setuptools库创建的包,第三方库常用的格式,添加了元数据信息的zip文件。
路径顺序为:

  1. 程序主目录,程序运行的主程序脚本所在的目录
  2. PYTHONPATH目录,环境变量PYTHONPATH设置的目录也是搜索模块的路径
  3. 标准库目录,Python自带的库模块所在目录。

3. 模块运行

  name,每个模块都会定义一个__name__特殊变量来存储当前模块的名称,如果不指定,则默认为源码文件名,如果是包则有限定名。
解释器初始化的时候,会初始化sys.modules字典(保存已加载的模块),加载builtins(全局函数、常量)模块、main__模块、sys模块、以及初始化模块搜索路径sys.path
Python是脚本语言,任何一个脚本都可以直接执行,也可以作为模块导入。当从标准输入(命令行敲代码)、脚本($ python test.py)或交互式读取的时候,会将模块的__name__设置为__main
,模块的顶层代码就 在__main__这个作用域中执行。顶层代码:模块中缩进最外层的代码。如果是import导入的,其__name__默认就是模块名。

if name == ‘main’:用途
  1. 本模块的功能测试:对于非主模块,测试本模块内的函数、类
  2. 避免主模块变更的副作用:顶层代码,没有封装,主模块使用时没有问题。但是,一旦有了新的主模块,老的主模块成了被导入的模块,由于原来代码没有封装,一并执行了。

4. 包

  pycharm中,创建Directiory和创建Python package不同,前者是创建普通的目录,后者是创建一个带有__init__.py文件的目录即包。

4.1 子模块

包目录下的py文件、子目录都是其子模块。

4.2 模块和包的总结

  包能够更好的组织模块,尤其是大的模块,其代码行数很多,可以把它拆分成很多子模块,便于使用某些功能就加载相应的子模块。包目录中 init.py 是在包第一次导入的时候就会执行,内容可以为空,也可以是用于该包初始化工作的代码,最好不要删除它(低版本不可删除)。导入子模块一定会加载父模块,但是导入父模块一定不会导入子模块。包目录之间只能使用.点号作为间隔符,表示模块及其子模块的层级关系。模块也是封装,如同类、函数,不过它能够封装变量、类、函数。模块就是命名空间,其内部的顶层标识符,都是它的属性,可以通过 dict 或dir(module)查看。包也是模块,但模块不一定是包,包是特殊的模块,是一种组织方式,它包含 path 属性。

# import a  # 注意目录没有__file__属性
#
#
# print(dir(a))
# print(type(a))
# print(a)
# print(a.__path__)
# print(a.x)


# import m  # 注意只加载m,不会递归加载
# from m import m1
# import m.m1
# import m.m2.m21
import m
from m.m2 import m22  # 此时m2不在当前名词空间中
import sys


print(dir())  # 加载了m和m1,当前名词空间中只有m
# print(m.m1)

print(list(filter(lambda x: x.startswith('m'), sys.modules.keys())))
print(m.abc)
print(m.m2.y)
print(m.m2.__dict__.keys())
print(m.m2.m22)

注意:不要删除__init__.py文件

5. 绝对导入、相对导入

5.1 绝对导入

  在import语句或者from导入模块,模块名称最前面不是以.点开头的。绝对导入总是去模块搜索路径中找,当然会查看一下该模块是否已经加载。

5.2 相对导入

  只能在包内使用,且只能用在from语句中,使用.点号,表示当前目录内,…表示上一级目录,不要在顶层模块中使用相对导入。

from . import d  # 有相对导入的模块不能作为主模块了,直接运行会报错
from .. import e

# d = 1000
print(d.__file__)
print(e.__file__)

== 注意:包内之间相互引用,基本都是使用相对导入,一旦一个模块中使用了相对导入,就不可以作为主模块运行了。==

6. 访问控制

6.1 下划线开头的模块名

  _ 或者 __ 开头的模块是否能够被导入呢?创建文件名为 _xyz.py 或者 __xyz.py 测都可以成功的导入,因为它们都是合法的标识符,就可以用作模块名。

print(__name__)
A = 5
_B = 20
__C = 300

print(dir())
# ['A', '_B', '__C', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', 
# '__loader__', '__name__', '__package__', '__spec__']

  普通变量、保护变量、私有变量、特殊变量,都没有被隐藏,也就是说模块内没有私有的变量,在模块中定义不做特殊处理。

# import test
#
# from test import A, _B, __C
from test import *  # 最前面带下划线的是无法导入的


print(dir())

6.2 from … import * 和__all__

  all__是一个列表,元素是字符串,每一个元素都是一个模块内的变量名。from xyz import *,如果xyz中定义了__all,则只导入__all__列表中的名称,没有__all__方法,则只能导入公有的。

# test.py
print(__name__)

__all__ = ['X', '_Y']

X = 5
_Y = 20
__Z = 300

__my__ = 500


def _a():
    pass


class __A:
    pass


print(dir())

# test2.py
from test import *  # 只能导入__all__列表中的名称,即X, _Y
import test


print(dir())
print(test._Y)
总结:

1、使用 from xyz import * 导入

  • 如果模块没有 all , from xyz import * 只导入非下划线开头的该模块的变量。如果是包,子模块也不导入,除非在
    all 中设置,或 init.py 中导入它们

  • 如果模块有 all , from xyz import * 只导入 all 列表中指定的名称,哪怕这个名词是下划线开头的,或者是子模块

  • from xyz import * 方式导入,使用简单,但是其副作用是导入大量不需要使用的变量,甚至有可能造成名称的冲突。而 all 可以控制被导入模块在这种导入方式下能够提供的变量名称,就是为了阻止from xyz import *导入过多的模块变量,从而避免冲突。因此,编写模块时,应该尽量加入 all

2、from module import name1, name2 导入
  这种方式的导入是明确的,哪怕是导入子模块,或者导入下划线开头的名称程序员可以有控制的导入名称和其对应的对象。
3. 模块变量的修改
  模块对象是同一个,因此模块的变量也是同一个,对模块变量的修改,会影响所有使用者。除非万不得已,或明确知道自己在做什么,否则不要修改模块的变量。前面学习过的猴子补丁,也可以通过打补丁的方式,修改模块的变量、类、函数等内容。

猜你喜欢

转载自blog.csdn.net/sqsltr/article/details/90673614