为了便于代码维护,一般很多函数分组,分别放到不同的文件里,每个文件包含的代码就相对较少,维护也更方便。在Python中,一个.py文件就称之为一个模块(Module)。
Python中模块分为三种: Python标准库,第三方模块,应用程序自定义模块
使用模块可以帮助避免函数与其他模块函数重名,函数名与变量名重名的问题。
此外,要尽量避免定义的模块名与内置函数(build in function)重名。
模块的引入使用import
相关语句。
在模块中可以包含可执行的语句和函数的定义,这些语句的目的是初始化模块,它们只在模块名第一次遇到导入import语句时才执行。
import语句是可以在程序中的任意位置使用,同一个模块可以被多次引用
为了防止在同一个文件中重复引入,python使用了一下优化方法:第一次导入后将模块名加载到内存,后续的import
语句仅是对已经加载到内存中的模块对象增加了一次引用,不会重新执行模块内的语句
import module1[, module2[, module3[, ...moduleN]]]
Python解释器通过import 语句
在搜索路径中搜索相应的模块,
模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path路径中包含的模块
若在当前目录下存在与要引入模块同名的文件,就会把要引入的模块屏蔽掉
from modulename import name1[, name2[, ... nameN]]
这个声明不会把整个modulename
模块导入到当前的命名空间中,只会将它里面的name1
或name2
单个引入到执行这个声明的模块的全局符号表
from modulename import *
通过使用*
提供了一个简单的方法来导入一个模块中的所有项目。然而这种声明不该被过多地使用。大多数情况, Python程序员不使用这种方法,因为引入的其它来源的命名,很可能覆盖了已有的定义。
别名的使用可以简化编程
两个模块xmlreader.py
和csvreader.py
,它们都定义了函数read_data(filename)
,用来从文件中读取一些数据,但采用不同的输入格式。可以通过为模块起别名实现使用同样的代码实现不同的功能,例如
if file_format == 'xml':
import xmlreader as reader
elif file_format == 'csv':
import csvreader as reader
data=reader.read_date(filename)
为了避免模块名冲突,Python引入了按目录来组织模块的方法,称为包(Package)。
无论是import
形式还是from...import
,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警觉:这是关于包才有的导入语法,点的左边都必须是一个包。
包的本质就是一个包含__init__.py
文件的目录。
需要注意的是from后import导入的模块不能带点,否则会有语法错误,如:from a import b.c
是错误语法
无论使用import
还是from import
,只要是第一次导入包或者是包的任何其他部分,都会依次执行包下的__init__.py
文件。
Foo #Top-level package
|__ __init__.py #Initialize the glance package
|
|__ api #Subpackage for api
| |__ __init__.py
| |__ read.py
| |__ write.py
|
|__ cmd #Subpackage for cmd
| |__ __init__.py
| |__ control.py
|
|__ db #Subpackage for db
|__ __init__.py
|__ data.py
此处是想从包api中导入所有,实际上该语句只会导入包api下__init__.py文件中定义的名字,我们可以在这个文件中定义__all___:
#在__init__.py中定义
x=10
def func():
print('from api.__init.py')
__all__=['x','read', 'func']
此时在与Foo同级的文件中执行from Foo.api import *就导入__all__中的内容,write仍然不能导入。
上述例子中最顶级包Foo是写给别人用的,然后在Foo包内部也会有彼此之间互相导入的需求,这时候就有绝对导入和相对导入两种方式:
绝对导入:以Foo作为起始
相对导入:用.或者..的方式最为起始(只能在一个包中使用,不能用于不同目录内)
例如:我们在Foo/api/write.py中想要导入Foo/cmd/control.py
在Foo/api/write.py
#绝对导入
from Foo.cmd import control
control.ctrl()
#相对导入
from ..cmd import control
control.ctrl()
特别需要注意的是:可以用import导入内置或者第三方模块,但是要绝对避免使用import来导入自定义包的子模块,应该使用from… import …的绝对或者相对导入,且包的相对导入只能用from的形式。
__all__是用于控制from…import * ,不能用于使用import单独导入包
创建如下目录结构
Foo
|---test.py
|---keystone
|---auth
| |---plugins
| | |---core.py
| | |---__init__.py
| |
| |---__init__.py
|
|---__init__.py
core.py内容为:
def create(cls=None, auth_payload=None, method_name=None):
print('function create')
pass
class UserAuthInfo:
def __init__(self):
self.password = None
def foo(self):
print('cls UserAuthInfo')
要求:import keystone,然后就可以直接调用keystone.create和keystone.UserAuthInfo
在keystone目录下的__init__.py
中输入如下代码
from .auth.plugins.core import create
from .auth.plugins.core import UserAuthInfo
在test.py中调用core.py中的函数
import keystone
keystone.create()
a = keystone.UserAuthInfo()
a.foo()
>>function create
>>cls UserAuthInfo
通常情况下,是不可以导入同级的模块的,但在某些场景也也是需要解决这个问题的,通过添加sys.path
可以解决这个问题,下面对这一问题详细阐述。
目录结构及调用关系如下所示
Foo
|---dir1
| |---hello.py
|---dir2
|---main.py
其中,hello.py:
def add(x,y)
return x+y
main.py如何能调用到hello.py中的add函数。
解决方法,添加同级目录的父级目录到sys.path
# main.py
import os
import sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 目录Foo
sys.path.append(BASE_DIR) # 添加Foo到`sys.path`
from dir1 import hello
print(hello.add(1, 2))
注意 上述解决方案中必须使用os.path.abspath(__file__)
确定当前文件的路径,如果不使用os.path.abspath
直接使用os.path.dirname(__file__)
在pycharm中是不会报错的,但如果在终端中测试就会发现是无法获取到想要的路径的,这算是pycharm的一个坑吧。
此外,pycharm会自动添加当前工程的工程目录到sys.path
,而在终端中打印sys.path
是没有这个目录的,如果要使用工程目录,请使用上述方法添加工程目录到sys.path
。
每个工程都应该在终端中做测试
Foo
|---Pac
| |---src
| | |---f1.py
| | |---f2.py
| | |---__init__.py
| |
| |---conf
| | |---settings.py
| | |---__init__.py
| |
| |---__init__.py
|
|---run
|---start_up.py
如果src
和conf
中的模块之间需要互相调用采用上述第五条阐述的包与包之间模块导入 解决方法,
如果是run
文件下的start_up.py
文件调用Pac
包下的模块,建议使用各个包中的__init__.py
。
具体方法如下:
src
包下的__init__.py
中使用from .f1 import *
,假如不需要导入全部函数,一个个导入。使用同样的方法操作f2.py
conf
包下的__init__.py
中使用上述方法处理自己包内的模块(py文件)Pac
包下的__init__.py
中使用from .src import *
导入f1
和f2
模块,或者直接使用模块名精确导入 sys.path
中,这之后就可以在start.py
中使用import Pac
导入包并以如下的方法调用方法import Pac
Pac.f1.hello()
这样做看起很麻烦,但这样可以有效利用模块的命名空间,可以一眼看出是调用的哪个模块下的方法。