Python3从入门到实战
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.6 模块和包

3.6.1 模块

1.模块

函数是可以重复调用的程序块。在程序中使用他人已经编写好的函数代码,或者使自己编写的函数能被其他程序使用的方法是:导入(import)该函数所在的模块(mudule)

Python模块(mudule)就是包含Python语句的、文件名后缀(文件扩展名)是.py的文件。这个文件中可以包含变量、函数和类等定义。

例如,创建一个叫作hi的模块,也就是创建一个hi.py文件:

def hello(name):
print("hello")
print(name)

模块hi里包含了一个叫作hello()的函数。

2.导入(import)模块

在程序中如果需要使用其他模块中的函数等功能,就需要用关键字import导入相应的模块。导入模块的一般格式是:

import模块名

要使用上面的模块hi中的hello()函数,首先要import(导入)hi模块,格式如下:

import hi

注意

导入的模块只要说明模块名即可,不能有文件扩展名.py。程序代码中如果要使用模块中的对象,如函数,则需要用.运算符,即使用“模块名.函数名”访问具体的函数。例如,使用hi.hello访问模块hi中的函数hello():

import hi
hi.hello(’小白’)

输出:

hello
小白

Python提供了许多标准模块,这些模块文件可以在Python安装目录的lib文件夹中找到。和导入自己编写的模块一样,可以导入那些别人编写好的模块。例如,导入一个用于数学计算的模块math,其中有一个表示圆周率的变量pi:

import math

print(math.pi)
print(math.sin(1.57)) #1.57约等于math.pi/2
print(math.sin(math.pi/2))

输出:

3.141592653589793
0.9999996829318346
1.0

如果忘记在函数名前添加“模块名.”,则会报错:“名字xxx未定义”。例如:

import math
print(sqrt(2))   #sqrt是求平方根函数

输出:

----------------------------------------------------------------------
NameError              Traceback(most recent call last)
<ipython-input-8-692e4d48a502> in <module>()
      1 import math
----> 2 print(sqrt(2))   #sqrt是求平方根函数

NameError: name 'sqrt' is not defined

3.重命名导入模块

可以用“import ...as”对一个导入进来的模块重新命名。例如:

import math as m          #将导入的模块math命名为m
print(m.pi)              #通过m而不是math去引用其中的名字

输出:

3.141592653589793

此时,只能用重命名的m.pi而不能用math.pi去访问math中的pi。

4.导入单独名字

可以用“from...import”从一个模块导入一个单独的名字,导入的这个名字就不用在前面添加“模块名.”前缀了。例如:

from math import sqrt    #从math模块导入名字sqrt
print(sqrt(2))           #sqrt是求平方根函数

输出:

1.4142135623730951

5.导入所有名字

还可以通过“from…import *”导入模块中的所有名字,该模块中的名字就可以直接使用,而不再需要模块名前缀了:

from math import *
print(pi)            #math模块中的pi变量
print(sin(1.57))     #math模块中的sin函数
print(sqrt(2))       #math模块中的sqrt函数

输出:

3.141592653589793
0.9999996829318346
1.4142135623730951

模块中的对象,如常量、函数、类等,可被不同的程序代码重复使用,即模块使得代码可以“复用”(如重复多次调用一个模块中的函数)。例如,前面多次使用的函数print()、math模块中的函数sqrt()等。

模块还有一个好处就是可以避免“名字冲突”,因为程序代码中不可避免地会使用他人的代码,如果不同的人使用了同一个名字就会引起冲突,但如果这些名字属于不同的模块,就可以通过模块名来区分它们。

因此,为了避免名字冲突,应该尽量避免“from...import”和“from ... import *”这种将名字直接导入的方法,而推荐使用“import ...”导入模块的方法。

前面介绍过Python的内置函数type()可以用于查询一个对象的类型,此外,还有两个函数dir()和help(),可分别用于显示一个对象的所有属性和查询一个对象(如函数)的文档字符串。通过这三个“内省函数”可以获得一个对象的帮助信息。

6.函数dir()

内置函数dir()返回一个对象的所有属性的名字。其函数格式是:

dir([object])

如果没有指定可选的参数对象object,则返回当前作用域中的所有名字,否则就返回指定的参数对象的object的所有属性。例如:

>>>dir()

返回当前作用域中的所有名字:

['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']

假如继续执行:

>>>alist=[2,5,8]
>>>dir()

则可以看到返回的名字的列表里多了一个新的名字alist:

['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'alist']

假如再执行:

>>>b=3.14
>>>dir()

则可以看到返回的名字的列表里又多了一个新的名字b:

['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__', 'alist', 'b']

假如在调用函数dir()时输入一个对象,则函数dir()返回这个对象的所有属性。例如:

>>>dir(alist)

将显示list类型对象alist的所有属性:

['__add__', '__class__', '__contains__', '__delattr__', '__delitem__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__',
'__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__',
'__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__sub-
classhook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert',
'pop', 'remove', 'reverse', 'sort']

假如再执行:

>>>dir(b)

将显示float类型对象b的所有属性:

['__abs__', '__add__', '__bool__', '__class__', '__delattr__', '__dir__',
'__divmod__', '__doc__', '__eq__', '__float__', '__floordiv__', '__format__',
'__ge__', '__getattribute__', '__getformat__', '__getnewargs__', …]

如果输入一个模块名,则显示这个模块中的所有属性,如模块中的函数名、类名、变量名等。例如:

>>>import sys
>>>dir(sys)

将显示sys模块的所有名字(属性):

['__displayhook__', '__doc__', '__excepthook__', '__interactivehook__',
'__loader__', '__name__', '__package__', '__spec__', '__stderr__', '__stdin__',
'__stdout__', '_clear_type_cache', '_current_frames',
…]

如果输入自定义的模块名hi:

>>>import hi
>>>dir(hi)

则显示hi模块的所有属性:

['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__',
 '__package__', '__spec__', 'hello']

7.函数help()

函数help()打印一个对象的帮助信息。例如,通过函数help()了解函数math.sin()的函数说明:

import math
help(math.sin)
Help on built-in function sin in module math:

sin(…)
    sin(x)

    Return the sine of x(measured in radians).

如果通过一个对象的__doc__属性直接打印一个对象的文档串,则显示类似的帮助文档。例如:

print(math.sin.__doc__)

输出:

sin(x)
Return the sine of x(measured in radians).

读者还可完成以下练习。

执行如下的help和dir命令,查询这些模块或函数的帮助文档或属性。

help(len)——内置函数len()的帮助文档,注意参数是len而不是len()。

help(sys)——系统模块sys(必须先导入sys)的帮助文档。

dir(sys)——系统模块sys里定义的符号(属性)。

help(sys.exit)——sys模块里的函数exit()的帮助文档。

help('xyz'.split)——字符串的split()方法的帮助文档,可以通过str类型或str类型的对象,如xyz调用这个方法help('xyz'.split),等价于help(str.split)。

help(list)——list对象的帮助文档。

dir(list)——list对象的所有属性,包括对象的所有方法和属性。

help(list.append)——list对象的append()方法。

通过这些帮助信息,就可以了解如何使用某个函数或对象的方法或属性。例如,使用list的append()方法可以向一个list对象的最后添加一个元素:

alist=[1,2,3]
alist.append('hello')
print(alist)

输出:

[1, 2, 3, 'hello']

8.模块的__name__属性

每个模块都有一个__name__属性,当这个模块被其他程序(模块)通过import方式导入时,其值就是这个模块的模块名。例如:

>>> print(hi.__name__)

输出:

hi

但如果一个模块作为主程序执行,即在控制台窗口通过Python解释器运行这个脚本时,这个模块的属性__name__就是main,即说明这个模块是主程序。例如,编写如下的脚本文件hi.py:

def hello(name):
    print("hello")
    print(name)

print(__name__)  #打印该模块自身的__name__属性

在控制台窗口运行这个脚本:

python hi.py

输出结果如下:

__main__

因此,在编写脚本文件时,经常在程序中根据__name__==__main__判断脚本是否作为主程序运行从而执行特定的程序代码。例如,修改上述的hi.py代码:

def hello(name):
    print("hello")
    print(name)

print(__name__)
if(__name__=='__main__'):
    hello('Li ping')
else:
  print('hi.py作为模块导入而不是作为主程序运行!')

该程序通过if(__name__=='__main__')判断脚本是否在主程序执行,如果作为主程序执行,则执行hello(‘Li Ping'),否则就执行另外的print()语句。例如,执行:

python hi.py

输出:

__main__
hello
Li Ping

3.6.2 sys模块(Python解释器接口)

sys模块负责与Python解释器的交互,提供一系列的函数和变量,用于操控Python的运行时环境。

1.sys.argv

在Python解释器下执行一个脚本时,会通过sys.argv变量向这个脚本传递一个命令行参数列表。其中,第一个元素sys.argv[0]是脚本程序的完整路径或文件名(取决于操作系统)。例如,编写一个叫作abc.py的脚本:

import sys
print(’脚本名:', sys.argv[0])

在Python解释器中执行这个脚本:

python abc.py

在Windows系统上将输出下面的信息:

脚本名:abc

如果执行这个脚本时,新提供了其他的参数,那么从第二个元素sys.argv [1]起就是这些新提供的参数。例如,abc.py脚本内容如下:

import sys
print(’脚本名:', sys.argv[0])
print('Hello ', sys.argv[1])

在Python解释器中执行如下脚本:

python abc.py wang

程序代码中sys.argv[0]是模块名abc,而sys.argv[1]是参数wang,在Windows系统上将输出下面的信息:

脚本名:abc
Hello wang

2.sys.path(模块搜索路径)

当import一个模块时,Python会在一些位置查找是否存在相应名字的模块,首先查找是否在内在(built-in)模块中,如未找到就在当前目录查找,如仍未找到,就在sys.path变量指定的目录里查找。

sys.path是一个字符串列表,用于指定模块的搜索路径,包括环境变量PYTHONPATH里的目录和安装目标的默认值。Python解释器在导入模块时,会在这些路径中查找相应的模块。

例如,可输出sys.path中的目录内容:

import sys
print(sys.path)

输出:

['', 'E:\\Anaconda\\python36.zip', 'E:\\Anaconda\\DLLs', 'E:\\Anaconda\\lib',
'E:\\Anaconda', 'E:\\Anaconda\\lib\\site-packages', 'E:\\Anaconda\\lib\\
site-packages\\win32', 'E:\\Anaconda\\lib\\site-packages\\win32\\lib',
'E:\\Anaconda\\lib\\site-packages\\Pythonwin', 'E:\\Anaconda\\lib\\
site-packages\\IPython\\extensions', 'C:\\Users\\s\\.ipython']

sys.path是一个字符串的list,其中,每个元素表示一个文件路径,也可以在自己编写的程序中将特定的文件路径通过list的append()添加到这个list中。

sys.path.append("python/my_code")
print(sys.path)

输出:

['', 'E:\\Anaconda\\python36.zip', 'E:\\Anaconda\\DLLs', 'E:\\Anaconda\\lib',
'E:\\Anaconda', 'E:\\Anaconda\\lib\\site-packages', 'E:\\Anaconda\\lib\\
site-packages\\ win32', 'E:\\Anaconda\\lib\\site-packages\\win32\\lib',
 'E:\\Anaconda\\lib\\site-packages\\ Pythonwin', 'E:\\Anaconda\\lib\\
site-packages\\IPython\\extensions', 'C:\\Users\\s\\ .ipython', 'python/my_code']

3.sys.exit()(退出函数)

退出函数sys.exit()用于退出Python脚本程序,该函数可以带一个整数作为参数,用于表示程序退出的状态(不同的整数表示不同的退出情形)。不同操作系统用不同整数的表示程序退出状态。通常,传递整数0表示程序正常退出。当调用函数sys.exit()时,它将引发SystemExit异常,该异常允许清理函数在异常处理的try / except模块的finally子句中起作用。例如:

import sys
sys.exit(0)

抛出错误异常:

An exception has occurred, use %tb to see the full traceback.
SystemExit: 0

如果要退出解释器而不是单个脚本程序,则可以使用内置函数exit(),该内置函数直接退出并关闭解释器:

exit(0)

4.sys.executable

sys.executable中保存Python解释器的完整路径。例如:

import sys
sys.executable

输出:

'C:\\Users\\s\\Anaconda3\\python.exe'

5.sys.platform

sys.platform值为平台标识符。例如:

import sys
sys.platform

输出:

'win32'

通过检查sys.platform值,可根据不同平台导入不同的模块,或者根据不同平台执行不同的处理代码。例如:

os=sys.platform
if os=="win32":
    # 使用Windows平台相关的代码或模块
    pass
elif os.startswith('linux'):
    # 使用Linux平台相关的代码或模块
    import subprocess
    subprocess.Popen(["ls, -l"])

6.sys.getrefcount()

函数sys.getrefcount()返回一个对象的引用计数,该计数通常比用户预期的多一个,因为它包含作为函数getrefcount()形参的临时引用。例如:

import sys
a=20489                 #a引用的20489的引用计数为1
sys.getrefcount(a)       #实参a传递给函数形参,使20489的引用计数为2

输出:

2

继续执行下面的命令:

b=a
sys.getrefcount(b)        #b引用了a引用的对象,20489的引用计数变为3

输出:

3

当a=20489引用创建的字符串对象20489时,该对象的引用计数为1;将a作为实参传递给sys.getrefcount(obj)时,形参obj又引用了a引用的对象,使得20489的引用计数变为2;当sys.getrefcount(b)时,即b引用a引用的对象时,20489的引用计数变为3;当一个对象的引用计数变为0时,Python才开始销毁这个对象,并回收这个对象占用的内容。

7.sys.getsizeof()

函数sys.getsizeof()返回一个对象占用的内存的字节数。例如:

import sys
a=25
sys.getsizeof(a)

输出:

28

即整数a占用了28字节。

8.sys.stdin、sys.stdout、sys.stderr

sys.stdin、sys.stdout、sys.stderr分别映射到与解释器的标准输入、标准输出和错误流相对应的文件对象。除脚本外,sys.stdin用于提供给解释器的所有输入,而sys.stdout用于函数print()的输出。解释器自己的提示及其错误消息转到sys.stderr。例如:

import sys
user_input=sys.stdin.readline()

程序等待用户输入,如果输入如下内容:

hello world

然后,执行下列语句:

print("Input : " + user_input)

则输出:

Input : hello world

9.sys.getdefaultencoding()

函数sys.getdefaultencoding()获取系统当前编码,系统默认编码是UTF-8。

sys.getdefaultencoding()

输出:

'utf-8'

10.sys.setdefaultencoding()

函数sys.setdefaultencoding()设置系统默认编码。例如,执行setdefaultencoding('utf8')时,则将系统默认编码设置为UTF-8。

3.6.3 伪随机数发生器模块

1.random模块

random模块为不同的分布提供伪随机数生成器。例如,为整数提供从一个范围里均匀地选取一个整数的函数;为序列提供均匀地选择一个元素、生成随机排列、用于随机抽样的函数;为实数提供计算均匀、正态(高斯)、对数正态、负指数、伽马和贝塔分布的函数。其中,为了生成角度分布,可以使用von Mises分布。

几乎所有模块函数都依赖于基本函数random(),它在半开放范围[0.0,1.0)内均匀生成随机浮点数。Python使用Mersenne Twister作为核心生成器,它产生53位精度浮点数,周期为219937-1,其底层的C实现线程既快又安全。Mersenne Twister是最广泛使用的随机数发生器之一,但因其具有完全确定性,所以它不适用于所有目的,且完全不适用于加密。

下面是random模块的一些常用随机数函数。

● random.random()生成[0.0, 1.0)之间的浮点数。

● random.uniform(a, b)如果a<b,则生成[a, b]之间的浮点数,否则,生成[b, a]之间的浮点数。

● random.choice(sequence)从序列sequence中获取一个随机元素。

● random.randrange([start], stop[, step])在range(start, stop, step)中随机选择一个元素,等价于choice(range(start, stop, step))。

例如,random.randrange(10, 100, 2),结果相当于从[10, 12, 14, 16, ..., 96, 98]序列中获取一个随机数。

● random.randint(a, b)生成整数a和b之间的整数,且必须满足a<=b,相当于randrange(a, b+1)。

● random.shuffle(x[, random])随机置换序列x(将序列x中元素的位置随机置换/打乱顺序),可选参数random是一个返回[0.0,1.0)之间浮点数的随机函数,默认就是random.random()函数。

● random.sample(x, k)从总体序列或集合x中随机选择k个元素,该函数可用于无替换随机抽样。函数random.sample()不会修改原有序列,而只是返回一个list对象。

下面是一个简单的使用示例。

import random
a=random.random()          #[0.0, 1.0)之间的随机浮点数(除1外)
b=random.uniform(100, 1)   #[1.0, 100.0)之间的浮点数,可能不包括100.0
c=random.randint(-10, 80)   #随机整数
print(a, '\t', b, '\t', c)
r=random.choice(r'dfs*d=! kh#^h@')  #在字符串r'dfs*d=! kh#^h@’中随机选择一个
print(r)
p=["Python", "C", "小白", "a.hwdong.com"]
print(random.choice(p))      #从列表x中任意选择1个数
random.shuffle(p)            #重排序:打乱列表x中元素的顺序
print(p)
alist=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a=random.sample(alist, 5)  #从list中随机获取5个元素,返回1个list对象
print(a)

输出:

0.3492832859352474    77.17952854183415    66
s
小白
['Python', 'C', ’小白’, 'a.hwdong.com']
[10, 8, 5, 3, 9]

2.种子(seeding)

每次调用random()等随机数生成函数时都会产生不同的值,这对于生成不断变化的随机数是有用的,但有时则希望以不同方式处理相同的数据集。例如,对于两个不同的算法,从数据集随机取出相同的一组数据,然后比较不同算法对这组数据处理的差别。这时可以用函数seed()初始化随机数发生器,以便让它产生预期的一组值。例如,两次运行下面的程序,都会产生一样的一组随机数:

import random
def f():
    random.seed(1)
    for i in range(5):
      print('{:04.3f}'.format(random.random()), end=' ')
    print()

    for i in range(3):
      print(random.randint(-5, 5), end=' ')
    print()
f()
f()

输出:

0.134 0.847 0.764 0.255 0.495
2 2 5
0.134 0.847 0.764 0.255 0.495
2 2 5

3.6.4 包

1.包(package)

一个大的软件项目会包含很多模块文件,如果将这些模块文件都放在一个文件夹中,则难以管理并跟踪它们。如同计算机中的资源管理器使用不同文件夹分类管理文件一样,这些模块文件也可以放在不同文件夹(目录)中。

Python以包的形式将相关模块文件组合在不同文件夹中。Python的包的概念很简单,可以理解为,包就是一个文件夹,其中包含了一个__init__.py文件。这个__init__.py文件可以是一个空文件,也可以包含一些Python语句。也就是说,包就是一个包含__init__.py文件的文件夹,包名就是文件夹名。在一个包(文件夹)中可能还包含其他的包(文件夹)或模块文件,这些包中又会包含其他的包和模块文件,从而形成一个层次性的目录结构。

创建包很简单,就是利用操作系统自带的层次性文件系统创建包对应的文件夹。例如,创建一个叫作myProj的文件夹,在该文件夹下创建一个空的__init__.py和两个模块文件hello.py、hi.py。这个叫作myProj的文件夹就是一个包,包名就是文件夹名myProj,文件夹和其中的文件形成如下的目录结构。

myProj
 |_ __init__.py
 |_ hello.py
 |_ hi.py

例如,hello.py的内容如下:

def hello(name):
  print('hello, ', name)

def f():
  print(’你好’)

例如,hi.py的内容如下:

def welcome(city):
    print('hi, welcome to ', city)

例如,这个文件夹myProj的路径已经在sys.path的某个路径中(如果该文件夹路径不在sys.path中,则可通过sys.path.a ppend()将其添加到这个对象中),在程序中就可以导入这个包中的模块文件,并调用其中的对象(如函数):

import myProj.hello, myProj.hi
myProj.hello.hello('Li ping')
myProj.hi.welcome('ShangHai')

输出:

hello, Li ping
hi, welcome to ShangHai

当然,也可以采用from...import导入单个模块或名字。

例如,导入单个模块:

from myProj import hello
hello.hello('Li ping')

输出:

hello, Li ping

可以导入单个模块并重命名。例如:

from myProj import hello as he
he.hello('Li ping')

输出:

hello, Li ping

也可以导入模块中的具体对象(如函数)。例如:

from myProj.hello import hello as heo
heo('Li ping')

输出:

hello, Li ping

当然,还可以导入整个包。例如:

import myProj
myProj

尽管语法上没有问题,但是这种方式并没有将该包中的模块名导入当前的名字空间中,因此,不能访问该包中的模块:

myProj.hello.hello('Li ping')

这种导入包的方式没有用处。

2.包的初始化

如果程序包目录中存在名为__init__.py的文件,则在导入程序包或程序包中的模块时会执行该文件。该功能可以用于执行包初始化代码,如包级数据的初始化。

例如,__init__.py的代码如下:

print(’调用__init__.py for {__name__}')
NAMES=['Li Ping', 'Wang Hao', 'Zhang Ping']

当导入包或包中的模块时,会首先执行__init__.py中的代码,如上面的输出语句和初始化一个全局变量NAMES:

import myProj

输出:

调用__init__.py for myProj

执行:

myProj.NAMES

输出:

['Li Ping', 'Wang Hao', 'Zhang Ping']

模块中的代码可以访问用import导入的包中的全局变量,如myProj.NAMES。例如,修改hello.py模块文件,导入myProj的NAMES全局变量:

def hello(name):
  print('hello, ', name)

def f():
  from myProj import NAMES
  print(NAMES[0])

执行:

from myProj import hello
hello.f()

输出:

调用__init__.py for myProj
Li Ping

__init__.py也可用于实现从包中自动导入模块。import myProj仅将名称myProj放在调用程序的本地符号表中,而不导入任何模块。但是,如果myProj目录中的__init__.py包含以下内容:

print(f’调用__init__.py for {__name__}')
import myProj.hello, myProj.hi
NAMES=['Li Ping', 'Wang Hao', 'Zhang Ping']

则当执行import myProj时,包中的模块hello和hi就被自动导入了。下面的语句就能正常工作了:

import myProj
myProj.hello.hello('Li ping')

对于模块,可以通过import *将模块中的所有对象导入到本地符号表中。例如:

>>>dir()

显示本地符号表:

['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']

继续执行:

>>>from myProj.hello import *
>>>dir()

显示本地符号表:

['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'f', 'hello']

可以看到,myProj.hello模块中的对象,如函数hello()和f(),都已导入本地符号表中。

对于包也可使用import *。例如:

from myProj import *

显示结果:

['NAMES', '__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']

可以看到,已导入包myProj中的全局变量NAMES,但并没有导入包及其包含的模块中的所有对象。

3.__all__变量

Python规定,如果在__init__.py中定义了一个__all__变量,且该变量中包含一些模块名,那么使用import *,如from myProj import *,就会导入__all__变量中的模块中的所有对象。

例如,修改,__init__.py文件:

print(’调用__init__.py for {__name__}')
NAMES=['Li Ping', 'Wang Hao', 'Zhang Ping']
__all__=['hello', 'hi']

可以看到,此时本地符号中就包含了__all__变量中的模块名:

['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'hello', 'hi']

可以使用这些模块中的对象,如:

>>>hello.f()

输出:

Li Ping

同样,调用hi模块的函数welcome():

>>>hi.welcome('Bei Jing')

输出:

hi, welcome to Bei Jing

在__init__.py中定义的__all__变量是针对整个包的,也可以在单独的模块文件中定义__all__变量,以说明该模块文件中的哪些对象可以被其他程序import。例如,修改hello.py文件:

__all__=['hello']

def hello(name):
  print('hello, ', name)

def f():
  from myProj import NAMES
  print(NAMES[0])

说明当该模块被其他程序导入时,只有函数hello()是可见的。

>>>dir()

输出:

['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']

执行:

from myProj.hello import *
dir()

输出:

['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'hello']

因此,可以执行:

hello(’李平’)

输出:

hello,李平

但不能执行:

f()

会产生错误:

Traceback(most recent call last):
  File "<pyshell#5>", line 1, in <module>
    f()
NameError: name 'f' is not defined

__all__总结

● 如果没有为一个包定义__all__,则当使用import *时不会导入任何对象。

● 如果没有为一个模块定义__all__,则当使用import *时将导入模块中的所有对象,否则仅导入__all__中定义的对象。

4.子包

包中可以嵌套子包(如同文件夹可以包含子文件夹一样),子包中还可以嵌套子包的子包。包之间可以一直这样嵌套下去(正如文件夹可以一直嵌套一样)。例如:

myProj
 |_ __init__.py
 |_ util
      |_ __init__.py
      |_ img_process.py
      |_ math_util.py
 |_ game
      |_ __init__.py
      |_ init_game.py
      |_ scene
        |_ __init__.py
        |_ init_scene.py
        |_ render.py
        |_ update.py
 |_ hello.py
 |_ hi.py

在myProj包中除三个模块文件__init__.py、hello.py和hi.py外,还有两个子包util和game,在每个子包下又有一些模块文件或包,如game包下有两个模块文件__init__.py、init_game.py和一个包scene,包scene下又有一些模块文件。

无论这种嵌套的包的层次结构多么复杂,都可以使用import语句,唯一需要注意的是,需要用额外的.表示完整的正确嵌套包含关系。例如:

导入单独的模块:

import myProj.util.math_util

导入单独的模块名:

from myProj.util import math_util

假设render模块有一个函数render(),则也可以直接导入单独的函数:

from myProj.game.scene.render import render

3.6.5 Matplotlib包

Python提供了一个专门用于绘图的工具包Matplotlib。Windows系统下以管理员权限打开命令行窗口,然后执行下列命令进行安装:

pip install matplotlib

Linux或Mac可以管理员权限执行下列命令进行安装:

sudo pip install matplotlib

matplotlib的pyplot模块提供了简单的绘图函数,可以用下面的代码导入matplotlib.pyplot模块并命名为plt,以避免在代码中写入一长串的matplotlib.pyplot。

import matplotlib.pyplot as plt

jupyter环境中可以用命令“%matplotlib inline”将图形嵌入在浏览器网页中。

%matplotlib inline

pyplot模块的函数plot()可以直接绘制2D数据。例如:

y=[i for i in range(10)]
print(y)
plt.plot(y)        # 绘制y作为纵轴坐标点构成的图形
plt.show()         # 调用plt.show()显示图形

输出并显示绘制图形:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

尽管只给了纵轴坐标的数组y,函数plot()默认自动生成从0开始的横轴坐标。当然,可以分别传递2个数组表示x和y坐标。例如:

x=[i*0.1 for i in range(10)]
y=[xi**2 for xi in x]
print(["{0:0.2f}".format(i)for i in x])
print(["{0:0.2f}".format(i)for i in y])
plt.plot(x, y)       #绘制(x, y)坐标点构成的图形
plt.show()           #调用plt.show()显示图形

输出:

['0.00', '0.10', '0.20', '0.30', '0.40', '0.50', '0.60', '0.70', '0.80', '0.90']
['0.00', '0.01', '0.04', '0.09', '0.16', '0.25', '0.36', '0.49', '0.64', '0.81']

可以绘制几个曲线。例如:

x=[i*0.2 for i in range(10)]
y=[xi**2 for xi in x]
y2=[3*xi-1 for xi in x]

plt.plot(x, y)           #绘制(x, y)坐标点构成的图形
plt.plot(x, y2)
plt.ylim(0,5)
plt.xlabel('x axis label')
plt.ylabel('y axis label')
plt.title('y=$x^2$ and y=3x-1')
plt.legend(['y=$x^2$', 'y=3x-1'])
plt.show()               #调用plt.show()显示图形

其中,pyplot模块的函数title()用于给图起一个标题,而函数legend()则给每个绘制的曲线起一个名字,函数xlim()和ylim()分别用于限定x和y坐标的范围,函数xlabel()和ylabel()分别用于给x轴和y轴分配一个标签。可以看到不同的图形将自动用不同的颜色显示。

函数plot()还可以接收一些参数,用于定制绘制的图形的样式。例如:

import math
x=[i*0.2 for i in range(50)]
y=[math.sin(xi)for xi in x]
y2=[math.cos(xi)for xi in x]
y3=[0.2*xi for xi in x]
plt.plot(x, y, 'r-')
plt.plot(x, y2, 'bo')
plt.plot(x, y3, 'g:')
plt.legend(['sin(x)', 'cos(x)', '0.2x'])
plt.show()

其中,'r-’的r表示红色(red); -表示短线;'bo’的b表示蓝色(blue)、o表示以圆点;'g:’的g表示绿色(green); :表示虚线。

pyplot模块除函数plot()可以绘图外,还有其他的一些函数也可用于绘制其他类型的图,如函数scatter()用于绘制散乱点图。例如:

import math
x=[i*0.2 for i in range(50)]
y=[math.sin(xi)for xi in x]
y2=[math.cos(xi)for xi in x]
y3=[0.2*xi for xi in x]
plt.plot(x, y, 'r-')
plt.plot(x, y2, 'bo')
plt.plot(x, y3, 'g:')
plt.legend(['sin(x)', 'cos(x)', '0.2x'])
plt.show()

总结

● 后缀是.py的Python程序文件称为模块。可以用关键字import导入一个模块(如xxx)到程序中,模块xxx中的名字如name,可以通过xxx.name访问。import xxx as yy用于在导入模块xxx时给模块xxx起一个别名yy,访问模块中的名字就要用这个别名作为前缀。

● from ... import可以导入模块中的一个名字(如from xxx import name导入模块xxx的单个名字name),也可以导入模块中的所有名字(如from xxx import *导入模块中的所有名字)。

● 包就是一个包含__init__.py文件的文件夹。这个__init__.py可以是空的文件,也可以包含一些Python命令(如执行一些初始化)。包将所有模块文件组织成一个层次结构。

● sys模块是Python解释器交互的接口,如向脚本程序传递命令行参数,添加工作路径等。

● random随机数模块可以用于生成各种随机数,如从一个序列对象里随机选择元素,或者得到一个序列的随机排列等。

● Python提供了一个专门用于绘图的工具包Matplotlib。