模块化

一般来说,编程语言中,库、包、模块是同一种概念,是代码组织方式。

Python中只有一种模块对象类型,但是为了模块化组织模块的便利,提供了”包”的概念。

模块module:指的是Python的源代码文件。

包package:指的是模块组织在一起的和包名同名的目录及其相关文件

导入语句语句
语句 含义
import 模块1[,模块2,…] 完全导入
import … as … 模块别名

import 语句

  1. 找到指定的模块,加载和初始化它,生成模块对象。找不到抛出异常
  2. 在import所在的作用域的局部命名空间中,增加名称和上一步创建的对象关联

单独运行下面例子,体会区别:

import  functools #导入模块
print(dir())
print(functools , type(functools))
print(functools.wraps)
#输出
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'functools']
<module 'functools' from '/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/functools.py'> <class 'module'>
<function wraps at 0x7fdc700f45e0>
import os.path  #导入os.path,os加入当前名词空间
print(dir())
print(os,type(os))
print(os.path) #完全限定名称访问path
#输出
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'os']
<module 'os' from '/usr/local/bin/../../../Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/os.py'> <class 'module'>
<module 'posixpath' from '/usr/local/bin/../../../Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/posixpath.py'>
import os.path as osp  #导入os.path并赋给osp
print(dir())  
print(osp)
import sys
import os.path
import test
def testimport():
    import os.path
    print(dir())
testimport()
print(globals().keys())
print(locals().keys())
print(dir())
print(*reversed(sys.modules.keys()))
#输出
['os']
dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'sys', 'os', 'test', 'testimport'])
dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'sys', 'os', 'test', 'testimport'])
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'os', 'sys', 'test', 'testimport']
test site _distutils_hack re copyreg sre_compile sre_parse sre_constants _sre enum _virtualenv importlib.util contextlib importlib.abc importlib.machinery importlib warnings types importlib._bootstrap_external importlib._bootstrap functools _functools collections _collections reprlib itertools heapq _heapq keyword operator _operator _bootlocale _locale _sitebuiltins os os.path posixpath genericpath _collections_abc stat _stat io abc _abc encodings.latin_1 __main__ _signal encodings.utf_8 encodings encodings.aliases codecs _codecs zipimport time _weakref _thread _frozen_importlib_external posix marshal _io _warnings _imp _frozen_importlib builtins sys

Process finished with exit code 0

总结:

  • 导入顶级模块,其名称会加入到本地名词空间中,并绑定到其模块对象
  • 导入非顶级模块,只将其顶级模块名称加入到本地名词空间中。导入的模块必须使用完全限定名称来访问
  • 如果使用了as,as后的名称直接绑定到导入的模块对象,并将该名称加入到本地名词空间中
  • import之后只能是模块类型
语句 含义
from … import … 部分导入
from … import … as … 别名
from语句
from pathlib import Path,PosixPath #在当前名词空间导入该模块的指定的成员
print(dir())
#输出
['Path', 'PosixPath', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
from pathlib import  *   #在当前名词空间导入该模块所有公共成员(非下划线开头的成员)或指定成员__all__
print(dir())
#输出
['Path', 'PosixPath', 'PurePath', 'PurePosixPath', 'PureWindowsPath', 'WindowsPath', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
from functools import wraps as wr,partial #别名

print(dir())
#输出
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'partial', 'wr']
from os.path import exists #加载、初始化os、os.path模块,exists加入本地名词空间并绑定

if exists('test'):
    print("Found")
else:
    print('Not Found')

print(dir())
print(exists)

import  os
#4钟方式获得同一个对象exists
print(os.path.exists)
print(exists)
print(os.path.__dict__['exists'])
print(getattr(os.path,'exists'))
#输出
Found
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'exists']
<function exists at 0x7fcd48168040>
<function exists at 0x7fcd48168040>
<function exists at 0x7fcd48168040>
<function exists at 0x7fcd48168040>
<function exists at 0x7fcd48168040>

总结:

  • 找到from子句中指定的模块,加载并初始化它(注意不是导入)
  • 对于import子句后的名称
    • 先查from子句导入的模块是否具有该名称的属性
    • 如果不是,则尝试导入该名称的子模块
    • 还没有找到,则抛出importError异常
    • 这个名称保存到本地名词空间中,如果有as子句,则使用as子句后的名称
from pathlib import  Path  #导入类Path

print(Path,id(Path))

import pathlib  as pl  #导入模块使用别名
print(dir())
print(pl)
print(pl.Path,id(pl.Path))
#输出   ,可以看出导入的名词Path和pl.Path是同一个对象
<class 'pathlib.Path'> 140408755686416
['Path', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'pl']
<module 'pathlib' from '/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/pathlib.py'>
<class 'pathlib.Path'> 140408755686416

自定义模块

自定义模块:.py就是一个模块

#module.py文件
print('This is modeules 模块')
class A:
    def showmodule(self):
        print(1,self.__module__,self)
        print(2,__class__,id(__class__))
if __name__ == '__main__':
    a = A()
    a.showmodule()
print(3,__name__)

#test2.py
import module
print(module.a)
print(__name__)
#输出
This is modeules 模块
1 module <module.A object at 0x7fd6c0070fa0>
2 <class 'module.A'> 140560824288896
3 module
<module.A object at 0x7fd6c0070fa0>
__main__

#test3.py
from module import A as cls
if __name__ == '__main__':
    a = cls()
    a.showmodule()


#输出
This is modeules 模块
3 module
1 module <module.A object at 0x7f8db008e6a0>
2 <class 'module.A'> 140246493782864

自定义模块命名规范

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

模块搜索顺序

使用sys.path 查看搜索顺序

import sys
print(*sys.path,sep='\n')
#输出
/Users/dujie/Desktop/t1/bin/python /Users/dujie/PycharmProjects/t1/bao.py 
/Users/dujie/PycharmProjects/t1
/Users/dujie/PycharmProjects/t1
/Library/Frameworks/Python.framework/Versions/3.8/lib/python38.zip
/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8
/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/lib-dynload
/Users/dujie/Desktop/t1/lib/python3.8/site-packages

显示结果为,python模块的路径搜索顺序

当加载一个模块的时候,需要从这些搜索路径中从前到后依次查找,并不搜索这些目录的子目录。

搜索到模块就加载,搜索不到就抛异常。

路径也可以为字典、zip文件、egg文件

.egg文件,有setuptools库创建的包,第三方库常用的格式。添加了元数据(版本号、依赖项等)信息的zip文件

路径顺序为:

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

sys.path可以被修改,增加新的目录

模块的重复导入

class A:
    def showmodule(self):
        print(1,self.__module__,self)
        print(2,self.__class__,id(__class__))
a = A()
a.showmodule()
import  module
print('localmodule')
import module
import module
#输出
1 __main__ <__main__.A object at 0x7fbe3801af70>
2 <class '__main__.A'> 140454665431680
This is modeules 模块
3 module
localmodule

print(*sys.modules.keys(),sep='\n')
#输出
...
importlib.abc
contextlib
importlib.util
_virtualenv
enum
_sre
sre_constants
sre_parse
sre_compile
copyreg
re
_distutils_hack
site
module

从执行结果看,不会产生重复导入的现象。

所有加在的模块都会记录到sys.modules中,sys.modules是存储已经加载过的所有模块的字典。

打印sys.modules可以看到builtins、os、os.path、sys等模块都已经加载了。

模块运行

__name__ 每个模块都会定义一个__name__ 特殊变量来存储当前模块的名称,如果不指定,则默认为源代码文件名,如果是包则有限定名。

解释器初始化的时候,会初始化sys.modules字典(保存已加载的模块),加载builtins(全局函数、常量)模块、__main__ 模块、sys模块,以及初始化模块搜索路径sys.path

Python是脚本语言,任何一个脚本都可以直接被执行,也可以作为模块被导入。

当从标准输入(命令行方式敲代码)、脚本($ python test.py)或交互式读取的时候,会将模块的__name__ 设置为__main__ ,模块的顶层代码就在__main__ 这个作用域中执行。顶层代码:模块中缩进最外层的代码

如果是import导入的,其__name__ 默认就是模块名

# test1.py文件
import test2
# test2.py文件
# 判断模块是否以程序的方式运行 $python test.py
if __name__ == '__main__':
    print('in __main__') # 程序的方式运行的代码
else:
    print('in imported module') # 模块导入的方式运行的代码

if __name__ == '__main__' :用途

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

模块的属性

属性 含义
__file__ 字符串,源文件路径
__cached__ 字符串,编译后的字节码文件路径
__spec__ 显示模块的规范
__name__ 模块名
__package__ 当模块是包,同__name__,否则可以设置为顶级模块的空字符串
import test3
for k,v in test3.__dict__.items():
    print(k,str(v)[:120])

print(dir(test3))
for name in dir(test3):
    print(getattr(test3,name))
#输出
This is modeules 模块
3 module
__name__ test3
__doc__ None
__package__ 
__loader__ <_frozen_importlib_external.SourceFileLoader object at 0x7fddf0070f70>
__spec__ ModuleSpec(name='test3', loader=<_frozen_importlib_external.SourceFileLoader object at 0x7fddf0070f70>, origin='/Users/d
__file__ /Users/dujie/PycharmProjects/t1/test3.py
__cached__ /Users/dujie/PycharmProjects/t1/__pycache__/test3.cpython-38.pyc
__builtins__ {'__name__': 'builtins', '__doc__': "Built-in functions, exceptions, and other objects.\n\nNoteworthy: None is the `nil'
cls <class 'module.A'>
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'cls']
{'__name__': 'builtins', '__doc__': "Built-in functions, exceptions, and other objects.\n\nNoteworthy: None is the `nil' object; Ellipsis represents `...' in slices.", '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>), '__build_class__': <built-in function __build_class__>, '__import__': <built-in function __import__>, 'abs': <built-in function abs>, 'all': <built-in function all>, 'any': <built-in function any>, 'ascii': <built-in function ascii>, 'bin': <built-in function bin>, 'breakpoint': <built-in function breakpoint>, 'callable': <built-in function callable>, 'chr': <built-in function chr>, 'compile': <built-in function compile>, 'delattr': <built-in function delattr>, 'dir': <built-in function dir>, 'divmod': <built-in function divmod>, 'eval': <built-in function eval>, 'exec': <built-in function exec>, 'format': <built-in function format>, 'getattr': <built-in function getattr>, 'globals': <built-in function globals>, 'hasattr': <built-in function hasattr>, 'hash': <built-in function hash>, 'hex': <built-in function hex>, 'id': <built-in function id>, 'input': <built-in function input>, 'isinstance': <built-in function isinstance>, 'issubclass': <built-in function issubclass>, 'iter': <built-in function iter>, 'len': <built-in function len>, 'locals': <built-in function locals>, 'max': <built-in function max>, 'min': <built-in function min>, 'next': <built-in function next>, 'oct': <built-in function oct>, 'ord': <built-in function ord>, 'pow': <built-in function pow>, 'print': <built-in function print>, 'repr': <built-in function repr>, 'round': <built-in function round>, 'setattr': <built-in function setattr>, 'sorted': <built-in function sorted>, 'sum': <built-in function sum>, 'vars': <built-in function vars>, 'None': None, 'Ellipsis': Ellipsis, 'NotImplemented': NotImplemented, 'False': False, 'True': True, 'bool': <class 'bool'>, 'memoryview': <class 'memoryview'>, 'bytearray': <class 'bytearray'>, 'bytes': <class 'bytes'>, 'classmethod': <class 'classmethod'>, 'complex': <class 'complex'>, 'dict': <class 'dict'>, 'enumerate': <class 'enumerate'>, 'filter': <class 'filter'>, 'float': <class 'float'>, 'frozenset': <class 'frozenset'>, 'property': <class 'property'>, 'int': <class 'int'>, 'list': <class 'list'>, 'map': <class 'map'>, 'object': <class 'object'>, 'range': <class 'range'>, 'reversed': <class 'reversed'>, 'set': <class 'set'>, 'slice': <class 'slice'>, 'staticmethod': <class 'staticmethod'>, 'str': <class 'str'>, 'super': <class 'super'>, 'tuple': <class 'tuple'>, 'type': <class 'type'>, 'zip': <class 'zip'>, '__debug__': True, 'BaseException': <class 'BaseException'>, 'Exception': <class 'Exception'>, 'TypeError': <class 'TypeError'>, 'StopAsyncIteration': <class 'StopAsyncIteration'>, 'StopIteration': <class 'StopIteration'>, 'GeneratorExit': <class 'GeneratorExit'>, 'SystemExit': <class 'SystemExit'>, 'KeyboardInterrupt': <class 'KeyboardInterrupt'>, 'ImportError': <class 'ImportError'>, 'ModuleNotFoundError': <class 'ModuleNotFoundError'>, 'OSError': <class 'OSError'>, 'EnvironmentError': <class 'OSError'>, 'IOError': <class 'OSError'>, 'EOFError': <class 'EOFError'>, 'RuntimeError': <class 'RuntimeError'>, 'RecursionError': <class 'RecursionError'>, 'NotImplementedError': <class 'NotImplementedError'>, 'NameError': <class 'NameError'>, 'UnboundLocalError': <class 'UnboundLocalError'>, 'AttributeError': <class 'AttributeError'>, 'SyntaxError': <class 'SyntaxError'>, 'IndentationError': <class 'IndentationError'>, 'TabError': <class 'TabError'>, 'LookupError': <class 'LookupError'>, 'IndexError': <class 'IndexError'>, 'KeyError': <class 'KeyError'>, 'ValueError': <class 'ValueError'>, 'UnicodeError': <class 'UnicodeError'>, 'UnicodeEncodeError': <class 'UnicodeEncodeError'>, 'UnicodeDecodeError': <class 'UnicodeDecodeError'>, 'UnicodeTranslateError': <class 'UnicodeTranslateError'>, 'AssertionError': <class 'AssertionError'>, 'ArithmeticError': <class 'ArithmeticError'>, 'FloatingPointError': <class 'FloatingPointError'>, 'OverflowError': <class 'OverflowError'>, 'ZeroDivisionError': <class 'ZeroDivisionError'>, 'SystemError': <class 'SystemError'>, 'ReferenceError': <class 'ReferenceError'>, 'MemoryError': <class 'MemoryError'>, 'BufferError': <class 'BufferError'>, 'Warning': <class 'Warning'>, 'UserWarning': <class 'UserWarning'>, 'DeprecationWarning': <class 'DeprecationWarning'>, 'PendingDeprecationWarning': <class 'PendingDeprecationWarning'>, 'SyntaxWarning': <class 'SyntaxWarning'>, 'RuntimeWarning': <class 'RuntimeWarning'>, 'FutureWarning': <class 'FutureWarning'>, 'ImportWarning': <class 'ImportWarning'>, 'UnicodeWarning': <class 'UnicodeWarning'>, 'BytesWarning': <class 'BytesWarning'>, 'ResourceWarning': <class 'ResourceWarning'>, 'ConnectionError': <class 'ConnectionError'>, 'BlockingIOError': <class 'BlockingIOError'>, 'BrokenPipeError': <class 'BrokenPipeError'>, 'ChildProcessError': <class 'ChildProcessError'>, 'ConnectionAbortedError': <class 'ConnectionAbortedError'>, 'ConnectionRefusedError': <class 'ConnectionRefusedError'>, 'ConnectionResetError': <class 'ConnectionResetError'>, 'FileExistsError': <class 'FileExistsError'>, 'FileNotFoundError': <class 'FileNotFoundError'>, 'IsADirectoryError': <class 'IsADirectoryError'>, 'NotADirectoryError': <class 'NotADirectoryError'>, 'InterruptedError': <class 'InterruptedError'>, 'PermissionError': <class 'PermissionError'>, 'ProcessLookupError': <class 'ProcessLookupError'>, 'TimeoutError': <class 'TimeoutError'>, 'open': <built-in function open>, 'quit': Use quit() or Ctrl-D (i.e. EOF) to exit, 'exit': Use exit() or Ctrl-D (i.e. EOF) to exit, 'copyright': Copyright (c) 2001-2021 Python Software Foundation.
All Rights Reserved.

Copyright (c) 2000 BeOpen.com.
All Rights Reserved.

Copyright (c) 1995-2001 Corporation for National Research Initiatives.
All Rights Reserved.

Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.
All Rights Reserved., 'credits':     Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
    for supporting Python development.  See www.python.org for more information., 'license': Type license() to see the full license text, 'help': Type help() for interactive help, or help(object) for help about object.}
/Users/dujie/PycharmProjects/t1/__pycache__/test3.cpython-38.pyc
None
/Users/dujie/PycharmProjects/t1/test3.py
<_frozen_importlib_external.SourceFileLoader object at 0x7fddf0070f70>
test3

ModuleSpec(name='test3', loader=<_frozen_importlib_external.SourceFileLoader object at 0x7fddf0070f70>, origin='/Users/dujie/PycharmProjects/t1/test3.py')
<class 'module.A'>

包,即特殊的模块

实验:

项目中新建一个目录m,使用下面的代码

import m # 目录
print(m)
print(type(m))
print(dir(m))
print(*m.__dict__.items(), sep='\n') # __file__为None
print('-' * 30)
import test1
print(*test1.__dict__.items(), sep='\n') # 文件模块没有__path__

竟然可以导入目录m,目录也是文件,所以可以导入,不过问题是,目录模块怎么写入代码?

为了解决这个问题,Python要求在目录下建立一个特殊文件 __init__.py ,在其中写入代码

image

pycharm中,创建Directory和创建Python package不同,前者是创建普通的目录,后者是创建一个带

有 __init__.py 文件的目录即包。

Python中,目录可以作为模块,这就是包,不过代码需要写在该目录下 __init__.py 中。

包的 __file__ 就指向 __init__.py 这个文件。

子模块

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

m
|-- __init__.py
|-- m1.py
|-- m2
   |-- __init__.py
   |-- m21
       |-- __init__.py
   |-- m22.py

如上建立子模块目录和文件,所有的py文件中就写一句话 print(__name__)

#注意观察已经加载的模块、当前名词空间的名词
#import m
#import m.m1
#import m.m2.m21
#from m import m1
from m.m2 import m21
print('-'*30)
print(*filter(lambda x:x.startswith('m'), dir()))
print('-'*30)
import sys
print(sorted(filter(lambda x:x.startswith('m'), sys.modules.keys())))

删除 __init__.py 试一试,可以发现删除并不影响导入,但是这不是良好的习惯,请保留

__init__.py 文件

模块和包的总结

包能够更好的组织模块,尤其是大的模块,其代码行数很多,可以把它拆分成很多子模块,便于使用某些功能就能加载相应的子模块。

包目录中__init__.py是在包的第一次导入的时候就会执行,内容可以为空,也可以是用于该包初始化工作的代码,最好不要删除它(低版本不可以删除__init__.py文件)

导入子模块一定会加载父模块,但是导入父模块一定不会导入子模块

包目录之间只能使用.点号作为分隔符,表示模块及其子模块的层级关系

模块也是封装,如果类、函数,不过它能够封装变量、类、函数。

模块就是命名空间,其内部的顶层标识符,都是他的属性,可以通过__dist__ 或dir(module)查看。

包也是模块,但模块不一定是包,包是特殊的模块,是一种组织方式,它包含__path__ 属性

问题

from json import encoder 之后, json.dump 函数用不了,为什么?

因为from后虽然引用了json,但是没有标识符,无法使用

原因是from json import encoder之后,当前名词空间没有json,但是json模块已经加载过了,没有

json的引用,无法使用dump函数。

import json.encoder 之后呢?json.dump函数能用吗?

因为import后 json在sys.module中,可以直接调用

import json.encoder也加载json模块,但是当前名词空间有json,因此可以调用json.dump。

绝对导入、相对导入

绝对导入
  • 在import语句或from导入模块,模块名称最前面不是以.点开头的
  • 绝对导入总是去模块搜索路径中找,当然会查看一下该模块是否已经加载
相对导入
  • 只能用在from语句中
  • 使用.点号,表示当前目录内
  • ..两点表示上一级目录
  • …三点表示上上一级
  • 只在包内使用,一般不要在顶层模块中使用相对导入
  • 一旦一个模块中使用相对导入,就不可以作为主模块运行了
# 举例a.b.c模块,a、b是目录,c是模块c.py
# c的代码如下
from . import d # imports a.b.d
from .. import e # imports a.e
from .d import x # a.b.d.x
from ..e import x # a.e.x

使用下面的结构的包,体会相对导入的使用

m
|-- __init__.py
|-- m1.py
|-- m2
   |-- __init__.py
   |-- m21
       |-- __init__.py
   |-- m22.py

测试一下有相对导入语句的模块,能够直接运行吗?

不能了,很好理解,使用相对导入的模块就是为了内部互相的引用资源的,不是为了直接运行的,对于

包来说,正确的使用方式还是在顶级模块使用这些包及其内部资源。

相对导入,更像是目录操作。

访问控制

下划线开头的模块名

_或者__开头的模块是否能够被导入呢?

创建文件名为_xyz.py或者__xyz.py测试

都可以成功的导入,因为他们都是合法的标识符,就可以用作模块名。

模块内的标识符
# xyz.py
print(__name__) A = 5
_B = 6
__C = 7
__my__ = 8
#test.py中
import xyz
import sys
print(sorted(sys.modules.keys()))
print(dir())
print(xyz.A)
print(xyz._B)
print(xyz.__C)
print(xyz.__my__)

普通变量、保护变量、私有变量、特殊变量,都没有被隐藏,也就是说模块内没有私有的变量,在模块

中定义不做特殊处理。

# test.py中
# from语句导入
from xyz import A, _B as B, __my__, __C as C
import sys
print(sorted(sys.modules.keys()))
print(dir())
print(A, B, __my__, C)

依然可以使用from语句,访问所有变量

from … import * 和 __all__

####### 使用from … import * 导入

# xyz.py
print(__name__)
 A = 5
_B = 6
__C = 7
__my__ = 8
# test.py中
from xyz import *
import sys
print(sorted(sys.modules.keys()))
print(dir())
print(locals()['A'])
A = 55
print(locals()['A']) # 思考这个A是谁的A了
xyz
['__main__', '_abc', '_bootlocale', '_codecs', '_collections', '_collections_abc', '_distutils_hack', '_frozen_importlib', '_frozen_importlib_external', '_functools', '_heapq', '_imp', '_io', '_locale', '_operator', '_signal', '_sitebuiltins', '_sre', '_stat', '_thread', '_virtualenv', '_warnings', '_weakref', 'abc', 'builtins', 'codecs', 'collections', 'contextlib', 'copyreg', 'encodings', 'encodings.aliases', 'encodings.latin_1', 'encodings.utf_8', 'enum', 'functools', 'genericpath', 'heapq', 'importlib', 'importlib._bootstrap', 'importlib._bootstrap_external', 'importlib.abc', 'importlib.machinery', 'importlib.util', 'io', 'itertools', 'keyword', 'marshal', 'operator', 'os', 'os.path', 'posix', 'posixpath', 're', 'reprlib', 'site', 'sre_compile', 'sre_constants', 'sre_parse', 'stat', 'sys', 'time', 'types', 'warnings', 'xyz', 'zipimport']
['A', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'sys']
5
55

结果是只导入了A,下划线开头的都没有导入

使用__all__

__all__ 是一个列表,元素是字符串,每一个元素都是一个模块内的变量名

# xyz.py
__all__ = ['_B']
print(__name__)
A = 5
_B = 6
__C = 7
__my__ = 8
from  xyz import  *
import sys
print(sorted(sys.modules.keys()))
print(dir())
print(locals()['_B'])

可以看到使用from xyz import *导入 __all__ 列表中的名称

包和子模块

包和模块结构如下

m
|-- __init__.py
|-- m1.py
# __init__.py中
print(__name__)
 x = 1
# m1.py中
print(__name__) 
y = 5
# test.py中
# 如何访问到m1.py中的变量y?
# 访问到m.m1的变量y的几种实现
import m
print(m.m1.y) # 可以吗?
# 方法1,直接导入m1模块
import m.m1
print(m.m1.y)
# 方法2,直接导入m.m1的属性y
from m.m1 import y
print(y)
# 方法3,from m import *
# print(dir())
# 该方法导入后,无法看到子模块m1,无法访问y
# 在__init__.py增加__all__ = ['x', 'm1'],使用__all__提供导出的名称
from m import *
print(m1.y)
# 方法4,不使用__all__
# 在__init__.py增加from . import m1
from m import *
print(m1.y)
# 不止以上4种,还有其他方式解决访问y,这里不再赘述

__init__.py 中有什么变量,则使用from m import *加载什么变量,这依然符合模块的访问控制

# __init__.py文件
print(__name__) x = 1
from .m1 import y as _z
print(dir())

####### 总结
一、使用from xyz import * 导入

  1. 如果模块没有 __all__from xyz import *只导入非下划线开头的该模块的变量。如果包,子模块也不会导入,除非在__all__ 中设置,或__init__.py 中导入他们
  2. 如果模块有 __all__from xyz import * 只导入 __all__ 列表中指定的名称,哪怕这个名词是下划线开头的,或者子模块
  3. from xyz import * 方式导入,使用简单,但是其副作用是导入大量不需要使用的变量,甚至有可能造成名称的冲突。而 __all__ 可以控制被导入模块在这种导入方式下能够提供的变量名称,就是为了阻止from xyz import * 导入过多的变量,从而避免冲突。因此,编写模块时,应该尽量加入__all__

二、from module import name1, name2 导入

这种方式的导入是明确的,哪怕导入子模块,或者导入下划线开头的 名称

程序员可以有控制的导入名称和其对应的对象