Python盒子:模块、包和程序

来源:互联网 发布:四川卫生统计网络直报 编辑:程序博客网 时间:2024/06/10 03:04

命令行参数

test.py

import sysprint('Program arguments:', sys.argv)

这里写图片描述

模块和import语句

在多个文件之间创建和使用Python代码。
一个模块仅仅是Python代码的一个文件

引用其它模块的代码时使用import语句,被引用模块中的代码和变量对该程序可见

导入模块

report.py

author = 'jason'def get_description():    '''Return random weather, just like the pros'''    # 我们只需要名为choice的函数,并无其他名为choice的函数,所以我们直接从random模块导入函数choice(), 无需random.前缀    from random import choice    possibilities = ['rain', 'snow', 'sleet', 'fog', 'sun', 'who knows']    # 此处改为random.return choice(possibilities)会出错    return choice(possibilities)    

weatherman.py

# 导入了整个report模块,但是需要把report.作为get_description()的前缀。通过模块名称限定模块的内容,可以避免命名冲突。import report# 可以调用report模块的函数get_descriptiondescription = report.get_description()print('Today\'s weather:', description)# 可以修改report模块的变量authorreport.author = 'Cui'print('Author: ', report.author)

这里写图片描述

如果被导入的代码被多次使用,就应该考虑在函数外部导入;如果被导入的代码使用有限,就在函数内部导入。

一些人更喜欢把所有的import都放在文件的开头,从而使代码之间的依赖关系清晰。

使用别名导入模块

在主程序weatherman.py中,我们调用了import report。但是,如果存在同名的另一个模块或者你想使用更短更好记的名字,该如何做呢?

import report as wrdescription = wr.get_description()print('Today\'s weather:', description)

导入模块的一部分

在Python中,可以导入一个模块的若干部分。每一部分都有自己的原始名字或者你想起的别名。

from report import get_description as do_itdescription = do_it()print('Today\'s weather:', description)

模块搜索路径

Python会在什么地方寻找文件来导入模块?可以从sys.path中可以看出,Python会通过上述目录进行依次查找,你可以读取和修改sys.path这个列表:
这里写图片描述
这里写图片描述

最开始的空白输出行是空字符串”,代表当前目录。因为空字符串是在sys.path的开始位置,Python会先搜索当前目录:import report会寻找文件report.py。

第一个匹配到的模块会先被使用,这也就意味着如果你在标准库之前的搜索路径上定义一个模块random,就不会导入标准库中的random模块。

为了使Python应用更具可扩展性,你可以把多个模块组织成文件层次,称之为

例子

也许我们需要两种类型的天气预报:一种是次日的,一种是下周的。一种可行的方式是新建目录sources,在该目录上新建两个模块daily.py和weekly.py。每一个模块都有一个函数forecast。

  • boxes
    • weather.py
    • sources
      • daily.py
      • weekly.py
      • _init_.py
# boxes/weather.pyfrom sources import daily. weeklyprint('Daily forecast:', daily.forecast())print('Weekly forecast:')# 从1开始枚举for number, outlook in enumerate(weekly.forecast(), 1):    print(number, outlook)
# boxes/sources/daily.pydef forecast():   'fake daily forecast'   return 'like yesterday'
def forecast():    '''Fake weekly forecast'''    return ['snow', 'more snow', 'sleet', 'freezing rain', 'rain', 'fog', 'hail']

sources目录下_init_.py

该文件可以为空,但Python需要它,以便把该目录作为一个包。

只要一个文件夹下面有个_init_.py 文件,那么这个文件夹就可以看做是一个包。包导入的过程和模块的基本一致,只是导入包的时候会执行此包目录下的_init_.py 而不是模块里面的语句了。

另外,如果只是单纯的导入包,而包的 _init_.py 中又没有明确的其他初始化操作,那么此包下面的模块是不会自动导入的

检验

  • test.py
  • PA
    • _init_.py
    • wave.py
    • PB1
      • _init_.py
      • pb1_m.py
    • PB2
      • _init_.py
      • pb2_m.py
# ./PA/PB1/__init__.pyprint('PB1->__init__.py')

1.

import PA.PB1.pb1_mprint('---')import PA.PB1print('---')

结果:

PB1->__init__.py------

2.

import PA.PB1print('---')import PA.PB1.pb1_mprint('---')

结果:

PB1->__init__.py------

3.

import PA.PB1

结果:

PB1->__init__.py

4.

import PA.PB1.pb1_m

结果:

PB1->__init__.py

综上,首次涉及包的调用就会执行_init_.py。

import机制详解

标准import

Python中所有加载到内存的模块都放在 sys.modules 。
当 import 一个模块时首先会在这个列表中查找是否已经加载了此模块
- 如果加载了则只是将模块的名字加入到正在调用 import 的模块(调用方)的 Local 名字空间中。
- 如果没有加载则从 sys.path 目录中按照模块名称查找模块文件,模块可以是py、pyc、pyd,找到后将模块载入内存,并加到 sys.modules 中,并将名称导入到调用方的 Local 名字空间。

一个模块不会重复载入

多个不同的模块都可以用 import 引入同一个模块到自己的 Local 名字空间,其实背后的 PyModuleObject 对象只有一个。

import 只能导入模块,不能导入模块中的对象(类、函数、变量等)

例如:模块 A(A.py)中有个函数 getName,另一个模块不能通过 import A.getName 将 getName导入到本模块,只能用 from A import getName。

嵌套import

顺序嵌套

例如:本模块导入 A 模块(import A),A 中又 import B,B 模块又可以 import 其他模块……

这中嵌套比较容易理解,需要注意的一点就是各个模块的 Local 名字空间是独立的

对于上面的例子,本模块 import A 之后本模块只能访问模块 A,不能访问模块 B 及其他模块。虽然模块 B 已经加载到内存了,如果访问还要再明确的在本模块中 import B

循环嵌套

例子

# A.pyfrom B import Dclass C: pass
# B.pyfrom A import Cclass D: pass

执行A.py将会出错。

而若把B.py中的from A import C修改为import B就不会出错。

这跟Python内部 import 的机制是有关的。

from B import D的执行方式

执行from B import D,Python 内部会分成几个步骤:

  • 在 sys.modules 中查找符号 “B”
    • 如果符号 B 存在,则获得符号 B 对应的 module 对象。从 的 dict 中获得符号 “D” 对应的对象,如果 “D” 不存在,则抛出异常。
    • 如果符号 B 不存在,则创建一个新的 module 对象 (注意: 此时,module 对象的 _dict_ 为空)。然后接着执行 B.py ,进而填充 的 dict。最后,再从 的 dict 中获得 “D” 对应的对象,如果 “D” 不存在,则抛出异常。

例子分析

1、执行 A.py 中的 from B import D,所以在 sys.modules 中并没有 存在, 首先为 B.py 创建一个 module 对象 () , 注意,这时创建的这个 module 对象是空的, 在 Python 内部创建了这个 module 对象之后,就会解析执行 B.py,其目的是填充 这个 dict
2、执行 B.py中的from A import C 在执行B.py的过程中,会碰到这一句, 首先检查sys.modules这个module缓存中是否已经存在了, 由于这时缓存还没有缓存, 所以类似的,Python内部会为A.py创建一个module对象(), 然后,同样地,执行A.py中的语句
3、再次执行A.py中的from B import D 这时,由于在第1步时,创建的对象已经缓存在了sys.modules中, 所以直接就得到了, 但是,注意,从整个过程来看,我们知道,这时还是一个空的对象,所以从这个module中获得符号”D”的操作就会抛出异常。 而如果这里只是import B,由于”B”这个符号在sys.modules中已经存在,所以是不会抛出异常的。

包 import

只要一个文件夹下面有个 init.py 文件,那么这个文件夹就可以看做是一个包。包导入的过程和模块的基本一致,只是导入包的时候会执行此包目录下的 init.py 而不是模块里面的语句了。

另外,如果只是单纯的导入包,而包的 _init_.py 中又没有明确的其他初始化操作,那么此包下面的模块是不会自动导入的

例子

有下面的包结构:

  • test.py
  • PA
    • _init_.py
    • wave.py
    • PB1
      • _init_.py
      • pb1_m.py
    • PB2
      • _init_.py
      • pb2_m.py

有如下程序:

# test.pyimport sys# 导入模块PA.waveimport PA.wave              #1# 导入包PA.PB1import PA.PB1               #2# 导入模块PA.PB1.pb1_mimport PA.PB1.pb1_m as m1   #3# 导入模块PA.PB2.pb2_mimport PA.PB2.pb2_m         #4PA.wave.getName()           #5m1.getName()                #6PA.PB.pb2_m.getName()       #7

1) 当执行 #1 后,sys.modules 会同时存在 PA、PA.wave 两个模块,此时可以调用 PA.wave 的任何类或函数了。但不能调用 PA.PB1(2) 下的任何模块。(当前 Local 中有了 PA 名字)

2) 当执行 #2 后,只是将 PA.PB1 载入内存,sys.modules 中会有 PA、 PA.wave、PA.PB1 三个模块,但是 PA.PB1 下的任何模块都没有自动载入内存,此时如果直接执行 PA.PB1.pb1_m.getName() 则会出错,因为 PA.PB1 中并没有 pb1_m 。(当前 Local 中还是只有 PA 名字,并没有 PA.PB1 名 字)

3) 当执行 #3 后,会将 PA.PB1 下的 pb1_m 载入内存,sys.modules 中会有 PA、PA.wave、PA.PB1、PA.PB1.pb1_m 四个模块,此时可以执行 PA.PB1.pb1_m.getName() 了。(由于使用了 as,当前 Local中除了 PA 名字,另外添加了 m1 作为 PA.PB1.pb1_m 的别名。)

4) 当执行 #4 后,会将 PA.PB2、PA.PB2.pb2_m 载入内存,sys.modules 中会有 PA、PA.wave、PA.PB1、PA.PB1.pb1_m、PA.PB2、PA.PB2.pb2_m 六个模块。(当前 Local 中还是只有 PA、m1)

下面的 #5,#6,#7 都是可以正确运行的。

关于Local

# test.pyprint(0, locals())print()import sysprint(1, locals())print()import PA.waveprint(2, locals())print()import PA.PB1print(3, locals())print()import PA.PB1.pb1_m     # 没有as m1,作为非一级名称,所以无需添加至localprint(4, locals())print()import PA.PB2.pb2_mprint(5, locals())print()

这里写图片描述

# test.pyprint(0, locals())print()import sysprint(1, locals())print()import PA.waveprint(2, locals())print()import PA.PB1print(3, locals())print()import PA.PB1.pb1_m as m1   # 有as m1,所以作为一级名称,添加至localprint(4, locals())print()import PA.PB2.pb2_mprint(5, locals())print()

这里写图片描述

python中import 不同目录中的模块的方法

主程序与模块程序在同一目录下

`-- src       |-- mod1.py       `-- test1.py  

若在程序test1.py中导入模块mod1, 则直接使用import mod1或from mod1 import *;

主程序所在目录是模块所在目录的父(或祖辈)目录

`-- src       |-- mod1.py       |-- mod2       |   `-- mod2.py       `-- test1.py  

若在程序test1.py中导入模块mod2, 需要在mod2文件夹中建立空文件init.py文件(使mod2成为包);

然后使用 from mod2.mod2 import * 或import mod2.mod2.

主程序导入上层目录中模块或其他目录(平级)下的模块

`-- src       |-- mod1.py       |-- mod2       |   `-- mod2.py       |-- sub       |   `-- test2.py       `-- test1.py  

若在程序test2.py中导入模块mod1和mod2。首先需要在mod2下建立init.py文件(使mod2成为包)。

import sys   # 把上级目录即src添加至搜素路径sys.path.append("..")   import mod1   import mod2.mod2  

同名问题

模块a 和 模块b 中都有 同名的 xxx 函数

from a import xxx as name_1from b import xxx as name_2

同名的模块a和模块a

利用构造的包进行区分

`-- src       |-- main.py       |-- a1       |   `-- a.py       |   `-- __init__.py    |-- a2       |   `-- a.py    |   `-- __init__.py
import a1.aimport a2.a

Python标准库

资料

  1. 官方文档
  2. 扼要使用指南
  3. Python Module of the Week
  4. The Python Standard Library by Example

使用setdefault()和defaultdict()处理缺失的键

setdefault()

读取字典中不存在的键的值会抛出异常。使用字典函数get()返回一个默认值会避免异常发生。与get()的区别在于,setdefault()当键不存在时它会在字典中添加一项。
这里写图片描述

defaultdict()

setdefault方法虽然在一定程度上解决了dict中不存在默认值的问题,但是这时候我们会想,有没有一种字典它本身提供了默认值的功能呢?答案是肯定的,那就是collections.defaultdict。(关于collections模块)

defaultdict()在创建字典时,对每个新的键都会指定默认值。它的参数是一个函数函数可以是自定义函数,也可以使用函数int()、list()或者dict()返回默认空的值(int()返回0,list()返回空列表[],dict()返回空字典{})。若不注明函数参数,新建的初始值会被设置为None。

这里写图片描述

顺便一提,也可以使用lambda来定义你的默认函数:
上面的例子可以转化为

y = defaultdict(lambda : 'default')

使用实例:计数器

# 普通字典dict_counter = {}for food in ['spam', 'spam', 'eggs', 'spam']:    if food not in dict_counter:        dict_counter[food] = 0    else:        dict_counter[food] += 1for food, count in dict_counter.items():    print(food, count)
# defaultdictfrom collections import defaultdictdict_counter = defaultdict(int)for food in ['spam', 'spam', 'eggs', 'spam']:    dict_counter[food] += 1for food, count in dict_counter.items():    print(food, count)

使用Counter()计数

这里写图片描述

most_common降序排序

函数most_common()以降序返回所有元素,或如果给定一个数字n,则会返回前n个结果。

>>> breakfast_counter.mostcommon()[('spam', 3), ('eggs', 1)]>>> breakfast_counter.mostcommon(1)[('spam', 3)]

组合两个计数器的结果

这里写图片描述
A + B:组合AB两个计数结果
A - B:A有而B没有AB均有而A比B多出的部分的计数结果
B - A:B有而A没有AB均有而B比A多出的部分的计数结果
A & B:A与B的交集,并取二者中的较小计数
A | B:A与B的并集,并取二者中的较大计数

使用有序字典OrderedDict()按键排序

一个字典中的键的顺序是不可预知的:你可以按照顺序添加键a、b和c,但函数keys()可能返回c、a和b。

有序字典OrderedDict()记忆字典键添加的顺序(与修改顺序无关),然后从一个迭代器按照相同的顺序返回。
这里写图片描述

双端队列:栈+队列

deque是一种双端队列,同时具有栈和队列的特征。它可以从序列的任何一端添加和删除项。

现在,我们从一个词的两端扫向中间,判断是否为回文。

def palindrome(word):    from collections import deque    dq = deque(word)    while len(dq) > 1:        if dq.popleft() != dq.pop():            return False    return True

其实,如果想要写一个快速的判断回文的程序,只需要把字符串反转和原字符串进行比较。

def palindrome(word):    return word == word[::-1]

使用itertools迭代代码结构(详细见)

itertools就是为了方便生成常用的迭代器的模块

示例:
1. itertools.chain([1, 2], [‘a’, ‘b’])等价于iter([1, 2, ‘a’, b’])—连接
2. itertools.cycle([1, 2])等价于iter([1, 2, 1, 2, …])—循环
3. itertools.accumlate([1, 2, 3, 4])等价于iter([1, 1+2, 1+2+3, 1+2+3+4]

# 可以把一个函数作为accumlate()的第二个参数,代替默认的加法函数import itertoolsdef multiply(a, b):    return a * b# 等价于iter([1, multiply(1, 2), mltiply(mulitiply(1, 2), 3), multiply(mulitiply(mulitiply(1, 2), 3), 4)]for item in itertools.accumlate([1, 2, 3, 4], multiply):    print(item)

使用pprint()友好输出(Data pretty printer)(详细见)

  • indent — 锁进
  • width — 一行最大宽度
  • depth — 打印的深度,这个主要是针对一些可递归的对象,如果超出指定depth,其余的用”…”代替。eg: a=[1,2,[3,4,],5] a的深度就是2; b=[1,2,[3,4,[5,6]],7,8] b的深度就是3
  • stream — 指输出流对象,如果stream=None,那么输出流对象默认是sys.stdout
  • compact

第三方Python库

  • PyPi
  • github
  • readthedocs
  • activestate (很多小代码示例)