1、模块
1.1、模块与模块导入
模块是包括Python定义和声明的文件。文件名就是模块名加上.py后缀。模块的模块名(作为一个字符串)可以由全局变量_name_得到。
相对Java的.java文件,python中的.py文件更类似于C中的头文件:
# 一个只有两个函数的模块,命名为fibo.py
def fib(n):
"""打印斐波那契数组"""
a, b = 0, 1
while b < n:
print(b, end=' ')
a, b = b, a+b
print()
def fib2(n):
"""生成斐波那契数组"""
result = []
a, b = 0, 1
while b < n:
result.append(b)
a, b = b, a+b
return result
# 在另外的文件中导入模块
import fibo
# 通过模块名直接访问函数
fibo.fib(5)
fib_ = fibo.fib2(5)
# 通过_name_全局属性查看模块名
fibo.__name__
# 还可以直接导入函数
from fibo import fib, fib2
# 就不需要通过模块名就可以直接调用
fib(50)
# 或者一次性导入所有
from fibo import *
注意:
- 一次性导入所有模块并不是推荐的做法
除此之外需要注意的是:
- 和Java运行jar文件类似,可以直接在python控制台直接运行.py文件:
python myproject.py <arguments>
如果想要接收控制台传入的参数的参数的话,需要引入sys模块,例如:
# 导入sys模块
import sys
print("命令行参数如下:")
# 通过sys.argv获取参数列表
for i in sys.argv:
print(i)
# 通过sys.path[0]获取文件路径
print('\n\nPython 路径为:',sys.path[0],'\n')
E:\pythonTest>python Test.py 1
命令行参数如下:
Test.py
1
Python 路径为: E:\pythonTest
- 每个import语句只会被执行一次,如果被import的模块中有可执行的代码块(除了函数定义以外的代码块,一般用于初始化全局参数),这些代码块在import之前就会执行。如果不希望一些代码块被执行,可以用__name__(注意这里是两个下划线)进行控制,(当__name__为“main”时表明该模块自身在运行,否则是被引入)。
if __name__ == '__main__':
这条语句,它的作用是,只有在当前模块名为__main__
的时候(即作为脚本执行的时候)才会执行此if
块内的语句。换句话说,当此文件以模块的形式导入到其它文件中时,if
块内的语句并不会执行。:
# 注意,只要当前模块被其他模块import了,下面的代码都会执行。和Java中的静态代码块有点类似。
print("程序在自身中运行,在其他文件中也运行")
# 这些代码只有在自身模块中运行。在其他模块中不运行
if __name__ == "__main__":
print('程序在自身运行')
- 除此之外还可以使用dir()函数找到模块内定义的所有名称,以一个字符串列表的形式返回:
import fibo
# 获取指定模块的可访问所有名称(全局变量和函数名)
print(dir(fibo))
# 如果不指定参数的话,dir()会返回当前模块可访问的所有名称
print(dir())
1.2、模块的搜索路径
在导入一个叫fibo的模块时,python解释器会先在当前目录中搜索名为fibo.py的文件。
如果没有找到的话,接着回到sys.path变量(如之前所提及,是当前能访问的路径的列表)中查找。sys.path变量的初始值(默认值)来自如下:
- 输入脚本的目录(当前目录)
- 环境变量PYTHONPATH表示的目录列表中搜索
- Python默认安装路径中搜索
1.3、标准模块
Python带有一个标准模块库,这些模块内置于解释器中。和Java中不同,python的这些标准模块库多数依赖于底层平台,是为了给系统调用等操作系统原生访问提供接口。例如winreg模块只在Windows系统上才有。
1.3.1 Linux下的os 模块
os
模块提供了与操作系统相关的功能。你可以使用如下语句导入它:
>>> import os
getuid()
函数返回当前进程的有效用户 id。
>>> os.getuid()
500
getpid()
函数返回当前进程的 id。getppid()
返回父进程的 id。
>>> os.getpid()
16150
>>> os.getppid()
14847
uname()
函数返回识别操作系统的不同信息,在 Linux 中它返回的详细信息可以从 uname -a
命令得到。uname()
返回的对象是一个元组,(sysname, nodename, release, version, machine)
。
>>> os.uname()
('Linux', 'd80', '2.6.34.7-56.fc13.i686.PAE', '#1 SMP Wed Sep 15 03:27:15 UTC 2010', 'i686')
getcwd() 函数返回当前工作目录。chdir(path)
则是更改当前目录到 path。在例子中我们首先看到当前工作目录是 /home/shiyanlou
,然后我们更改当前工作目录到 /Code
并再一次查看当前工作目录。
>>> os.getcwd()
'/home/shiyanlou'
>>> os.chdir('Code')
>>> os.getcwd()
'/home/shiyanlou/Code'
所以现在让我们使用 os 模块提供的另一个函数来创建一个自己的函数,它将列出给定目录下的所有文件和目录。
def view_dir(path='.'):
"""
这个函数打印给定目录中的所有文件和目录
:args path: 指定目录,默认为当前目录
"""
names = os.listdir(path)
names.sort()
for name in names:
print(name, end =' ')
print()
使用例子中的 view_dir()
函数。
>>> view_dir('/')
.bashrc .dockerenv .profile bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
os 模块还有许多非常有用的函数,你可以在这里阅读相关内容。
1.4、“编译的”Python文件
Python是一种解释型语言,这意味着它和Java、C#等都不同(Java中会先将代码编译为.class文件然后交由JVM解释为机器码执行,C#类似),python文件是在运行的时候将程序翻译成机器语言。
但是为了实现“跨平台”和“加快加载模块的速度”,python会在__pycache__目录下以”module.version.pyc”(例如__pycache__/fibo.cpytion-33.pyc)的形式缓存每个模块编译后的版本。
Python会自动通过检查源文件和编译版的修改日期来确定它是否过期并需要重新编译。
其余需要注意的是:
- 对于直接从命令行加载的模块,Python永远会重新编译而不会从缓存中读取
- 如果没有源模块(即只有编译版),python不会检查缓存(此时不检查也无所谓)。但是需要注意的是如果要支持没有源模块的发布,编译后的模块必须在源目录下。
- 编译缓存并不会使模块的运行速度变快,但是会使加载的速度变快
2、包
2.1、基本使用
和Java中的包的概念类似,python中的包同样用于更好的管理模块以及避免命名冲突。包的表示也和Java中的类似,例如A.B表示了名为A的包,和名为B的模块。
需要注意的和Java中不同的是,为了让Python把目录当做包,目录中必须包含__init__.py文件。一般情况下,只需要一个空的__init__.py文件即可,当然也可以在__init__.py文件中执行包的初始化代码。
引用包和引用模块一样,也有三种方式:
# 每次都导入特定的模块
import package.fibo
# 每次调用的时候都需要些完整路径
package.fibo.fib(5)
# fibo.fin(5)或者fib(5)都会报错
# 导入指定模块
from package import fibo
# 这种方式就无需写包前缀
fibo.fib(5)
# 还可以导入指定的函数、类或变量
from package.fibo import fib
# 这种方式就无需写任何前缀
fib(5)
需要注意的是:
- 使用import…的时候,import进来的必须是包或者模块
- 使用from…import…的时候,可以import进来包、模块以及模块中定义的其他命名(函数、类或者变量),推荐使用form…import…。
- 当import后面跟着的只到包时,并不会自动引入包下的所有模块。需要使用__all__属性指定想要导入的模块。
2.2、import * 与 __all__变量
在python中使用import * 时,如果没有定义 __all__
, from sound.effects import *
语句不会从 sound.effects
包中导入所有的子模块。
无论包中定义多少命名,只能确定的是导入了 sound.effects
包(可能会运行 __init__.py
中的初始化代码)以及包中定义的所有命名会随之导入。
如果希望使用import * 以节省敲打代码的工作量,可以在__init__.py文件中定义__all__变量,指定在使用import * 时导入的模块:
__all__ = ["echo", "surround", "reverse"]
虽然这样可以实现,但是实际工作中还是不推荐这种写法,推荐使用form import。
2.3、包内引用
在一个父包下,引用其他子包下的模块可以使用相对路径:
- . 表示同一个包下
- .. 表示上级目录下
- ..pakage 表示上级目录的pakage包下
3、标准模块和第三方模块查找
Python 吸引人的一点是其有众多的模块可以使用,对于自带模块,可以看看 Python3 的官方文档,对于第三方模块,可以在 PyPI 上找找。很多时候你都能找到合适的包帮你优雅的完成部分工作。比如 argparse
模块帮你非常容易的编写用户友好的命令行接口。