简单网站制作实验报告,广州网络推广引流,做兼职网站的项目初衷,陈铭生杨昭原型5、数据结构
本章更详细地描述了一些你已经学过的东西#xff0c;并添加了一些新的东西。
5.1. 更多关于Lists
列表(list)数据类型有更多的方法。下面是列表对象的所有方法: list.append(x) 在列表末尾添加一项。相当于a[len(a):] [x]。
list.extend(iterable) 通过添加可…5、数据结构
本章更详细地描述了一些你已经学过的东西并添加了一些新的东西。
5.1. 更多关于Lists
列表(list)数据类型有更多的方法。下面是列表对象的所有方法: list.append(x) 在列表末尾添加一项。相当于a[len(a):] [x]。
list.extend(iterable) 通过添加可迭代对象中的所有项来扩展列表。相当于a[len(a):] iterable。
list.insert(i, x) 在给定位置插入一个项目。第一个参数是要插入的元素的索引因此a.insert(0, x)插入到列表的前面a.insert(len(a) x)相当于a.append(x)。
list.remove(x) 从列表中移除值等于x的第一个元素。如果没有这样的元素将引发ValueError。
list.pop([i]) 删除列表中给定位置的项并返回它。如果没有指定索引a.pop()删除并返回列表中的最后一项。(方法签名中i周围的方括号表示该参数是可选的而不是您应该在该位置键入方括号。你会在Python库参考中经常看到这个符号。)
list.clear() 从列表中删除所有项目。相当于del a[:]。
list.index(x[, start[, end]]) 返回列表中第一个值等于x的项的从零开始的索引。如果没有这样的项则引发ValueError。
可选参数start和end被解释为切片表示法并用于将搜索限制为列表的特定子序列。返回的索引是相对于整个序列的开头计算的而不是相对于start参数计算的。
list.count(x) 返回x在列表中出现的次数。
list.sort(*, keyNone, reverseFalse) 对列表中的项进行排序(参数可用于自定义排序请参阅sorted()了解其解释)。
list.reverse() 将列表中的元素反转。
list.copy() 返回列表的浅拷贝。相当于a[:]。
一个使用了大多数列表方法的例子: fruits [orange, apple, pear, banana, kiwi, apple, banana]fruits.count(apple)
2fruits.count(tangerine)
0fruits.index(banana)
3fruits.index(banana, 4) # Find next banana starting a position 4
6fruits.reverse()fruits
[banana, apple, kiwi, banana, pear, apple, orange]fruits.append(grape)fruits
[banana, apple, kiwi, banana, pear, apple, orange, grape]fruits.sort()fruits
[apple, apple, banana, banana, grape, kiwi, orange, pear]fruits.pop()
pear你可能已经注意到像insert, remove或sort这样只修改列表的方法不会打印返回值-它们返回默认值None。这是Python中所有可变数据结构的设计原则。
您可能会注意到的另一件事是并非所有数据都可以排序或比较。例如[None ‘hello’ 10]不排序因为整数不能与字符串比较None不能与其他类型比较。此外还有一些类型没有定义排序关系。例如34j 57j不是一个有效的比较。
5.1.1 使用列表作为栈
列表方法可以很容易地将列表用作栈其中添加的最后一个元素是检索的第一个元素(“后进先出”)。要将一项添加到堆栈顶部请使用append()。要从堆栈顶部检索项请使用不带显式索引的pop()。例如:
stack [3, 4, 5]
stack.append(6)
stack.append(7)
stack
#[3, 4, 5, 6, 7]
stack.pop()
#7
stack
#[3, 4, 5, 6]
stack.pop()
#6
stack.pop()
#5
stack
#[3, 4]5.1.2 使用列表作为队列
也可以将列表用作队列其中添加的第一个元素是检索到的第一个元素(“先入先出”);然而对于这个目的列表不是有效的。虽然从列表末尾进行追加和弹出操作速度很快但从列表开头进行插入或弹出操作速度很慢(因为所有其他元素都必须移动一位)。
要实现队列请使用collections.deque它被设计为具有从两端快速追加和弹出的功能。例如:
from collections import deque
queue deque([Eric, John, Michael])
queue.append(Terry) # Terry arrives
queue.append(Graham) # Graham arrives
queue.popleft() # The first to arrive now leaves
# Eric
queue.popleft() # The second to arrive now leaves
# John
queue # Remaining queue in order of arrival
# deque([Michael, Terry, Graham])5.1.3 列表推导
列表推导式提供了一种创建列表的简明方法。常见的应用程序是创建新列表其中每个元素是应用于另一个序列或可迭代对象的每个成员的某些操作的结果或者创建满足特定条件的那些元素的子序列。
例如假设我们想创建一个正方形列表如下所示:
squares []
for x in range(10):squares.append(x**2)squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]注意这会创建(或覆盖)一个名为x的变量该变量在循环完成后仍然存在。我们可以在没有任何副作用的情况下使用:
squares list(map(lambda x: x**2, range(10)))或等价
squares [x**2 for x in range(10)]这样更简洁易读。
列表推导式由方括号组成括号中包含一个表达式后跟一个for子句然后是零个或多个for或if子句。结果将是在其后的for和if子句的上下文中对表达式求值所产生的新列表。例如下面的listcomp将两个不相等的列表中的元素组合起来:
[(x, y) for x in [1,2,3] for y in [3,1,4] if x ! y]
# [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]它等价于:
combs []
for x in [1,2,3]:for y in [3,1,4]:if x ! y:combs.append((x, y))combs# [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]注意在这两个代码片段中for和if语句的顺序是相同的。
如果表达式是一个元组(例如前面示例中的(x, y))则必须将其括起来。
vec [-4, -2, 0, 2, 4]
# create a new list with the values doubled
[x*2 for x in vec]# filter the list to exclude negative numbers
[x for x in vec if x 0]# apply a function to all the elements
[abs(x) for x in vec]# call a method on each element
freshfruit [ banana, loganberry , passion fruit ]
[weapon.strip() for weapon in freshfruit]# create a list of 2-tuples like (number, square)
[(x, x**2) for x in range(6)]# the tuple must be parenthesized, otherwise an error is raised
# 元组必须加括号否则会引发错误
[x, x**2 for x in range(6)]# flatten a list using a listcomp with two for
# 使用带有两个“for”的listcomp将列表平铺
vec [[1,2,3], [4,5,6], [7,8,9]]
[num for elem in vec for num in elem]
列表推导式可以包含复杂的表达式和嵌套函数:
from math import pi
[str(round(pi, i)) for i in range(1, 6)]# [3.1, 3.14, 3.142, 3.1416, 3.14159]5.1.4 嵌套列表推导式
列表推导式中的初始表达式可以是任意表达式包括另一个列表推导式。
考虑下面的例子一个3x4矩阵被实现为3个长度为4的列表:
matrix [[1, 2, 3, 4],[5, 6, 7, 8],[9, 10, 11, 12],
]下面的列表推导式将行和列进行转置:
[[row[i] for row in matrix] for i in range(4)]# [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]正如我们在上一节看到的嵌套的listcomp是在它后面的for语句的上下文中求值的所以这个例子等价于:
transposed []
for i in range(4):transposed.append([row[i] for row in matrix])transposed
# [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]这就相当于:
transposed []
for i in range(4):# the following 3 lines implement the nested listcomptransposed_row []for row in matrix:transposed_row.append(row[i])transposed.append(transposed_row)transposed在现实世界中您应该更喜欢内置函数而不是复杂的流语句。zip()函数将在这个用例中做得很好:
list(zip(*matrix))# [(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]有关本行星号的详细信息请参见解包参数列表。
5.2. del 语句
有一种方法可以从给定索引而不是值的列表中删除项:del语句。这与返回值的pop()方法不同。del语句还可以用于从列表中删除切片或清除整个列表(我们之前通过将空列表赋值给切片来完成)。例如:
a [-1, 1, 66.25, 333, 333, 1234.5]
del a[0]
a
# [1, 66.25, 333, 333, 1234.5]
del a[2:4]
a
# [1, 66.25, 1234.5]
del a[:]
a
# []del也可以用来删除整个变量:
del a此后引用名称a是错误的(至少在为其分配另一个值之前)。稍后我们将找到del的其他用途。
5.3 元组和序列
我们看到列表和字符串有许多共同的属性比如索引和切片操作。它们是序列数据类型的两个例子(参见序列类型-list, tuple, range)。由于Python是一种不断发展的语言因此可能会添加其他序列数据类型。还有另一种标准序列数据类型:元组tuple。
元组由若干以逗号分隔的值组成例如:
t 12345, 54321, hello!
t[0]t# Tuples may be nested:
u t, (1, 2, 3, 4, 5)
u# Tuples are immutable: 元组是不可变的
t[0] 88888
Traceback (most recent call last):File stdin, line 1, in module
TypeError: tuple object does not support item assignment# but they can contain mutable objects:
v ([1, 2, 3], [3, 2, 1])
v如你所见在输出时元组总是用圆括号括起来以便正确解释嵌套的元组;它们的输入可以带圆括号也可以不带圆括号尽管圆括号通常是必需的(如果元组是较大表达式的一部分)。不可能对元组中的单个项进行赋值但是可以创建包含可变对象的元组例如列表。
虽然元组看起来类似于列表但它们通常用于不同的情况和不同的目的。元组是不可变的通常包含一个异构的元素序列这些元素可以通过解包(参见本节后面的部分)或索引(甚至在namedtuples的情况下通过属性)来访问。列表是可变的它们的元素通常是同构的可以通过在列表上迭代来访问。
一个特殊的问题是构造包含0项或1项的元组:语法有一些额外的怪癖来适应这些问题。空元组由一对空括号构造;只有一项的元组是通过在值后面加上逗号来构造的(将单个值括在括号中是不够的)。丑陋但有效。例如: empty ()singleton hello, # -- note trailing commalen(empty)
0len(singleton)
1singleton
(hello,)语句t 12345, 54321, hello!是元组打包(tuple packing)的一个例子:值12345,54321和hello!被打包在一个元组中。相反的操作也是可能的:
x, y, z t这被适当地称为序列解包(sequence unpacking)并适用于右侧的任何序列。序列解包要求等号左侧的变量个数与序列中的元素个数一样多。注意多重赋值实际上只是元组打包和序列解包的组合。
5.4 集合Sets
Python还包括set的数据类型。set是没有重复元素的无序集合。基本用途包括成员测试和消除重复条目。Set对象还支持数学运算如并、交、差和对称差(union, intersection, difference, and symmetric difference)。
花括号或set()函数可用于创建集合。注意:要创建一个空集合必须使用set()而不是{};后者创建一个空字典这是一种数据结构我们将在下一节讨论。
下面是一个简短的演示: basket {apple, orange, apple, pear, orange, banana}print(basket) # show that duplicates have been removed
{orange, banana, pear, apple}orange in basket # fast membership testing
Truecrabgrass in basket
False # Demonstrate set operations on unique letters from two words
...a set(abracadabra)b set(alacazam)a # unique letters in a
{a, r, b, c, d}a - b # letters in a but not in b
{r, d, b}a | b # letters in a or b or both
{a, c, r, d, b, m, z, l}a b # letters in both a and b
{a, c}a ^ b # letters in a or b but not both
{r, d, b, m, z, l}与列表推导式类似也支持集合推导式: a {x for x in abracadabra if x not in abc}a
{r, d}5.5 字典
Python内建的另一个有用的数据类型是字典(dictionary参见映射类型-字典)。字典在其他语言中有时被称为“联想记忆”或“关联数组”。与序列不同的是序列是通过一系列数字来索引的字典是通过键来索引的键可以是任何不可变类型;字符串和数字总是可以作为键。如果元组只包含字符串、数字或元组则可以用作键;如果元组直接或间接包含任何可变对象则不能将其用作键。不能将列表用作键因为可以使用索引赋值、切片赋值或append()和extend()等方法就地修改列表。
最好将字典视为一组键值对(key: value)并且要求键是唯一的(在一个字典中)。一对大括号创建一个空字典:{}。在大括号内放置逗号分隔的键:值对列表将初始键值对添加到字典中;这也是字典在输出时写入的方式。
字典的主要操作是存储带有某个键的值并提取给定键的值。也可以使用del删除键值对。如果使用已经在使用的键进行存储则会忘记与该键关联的旧值。使用不存在的键提取值是错误的。
在字典上执行list(d)将返回字典中使用的所有键的列表按插入顺序(如果您希望对其排序只需使用sorted(d))。使用关键字in检查字典中是否存在单个键。 tel {jack: 4098, sape: 4139}tel[guido] 4127tel
{jack: 4098, sape: 4139, guido: 4127}tel[jack]
4098del tel[sape]tel[irv] 4127tel
{jack: 4098, guido: 4127, irv: 4127}list(tel)
[jack, guido, irv]sorted(tel)
[guido, irv, jack]guido in tel
Truejack not in tel
Falsedict()构造函数直接从键值对序列构建字典: dict([(sape, 4139), (guido, 4127), (jack, 4098)])
{sape: 4139, guido: 4127, jack: 4098}此外字典推导式可用于从任意键和值表达式创建字典: {x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}当键是简单字符串时有时使用关键字参数更容易指定对: dict(sape4139, guido4127, jack4098)
{sape: 4139, guido: 4127, jack: 4098}5.6 循环技术
在遍历字典时可以使用items()方法同时检索键和相应的值。 knights {gallahad: the pure, robin: the brave}for k, v in knights.items():
... print(k, v)
...
gallahad the pure
robin the brave当在一个序列中循环时,可以使用 enumerate() 函数同时检索位置索引和相应的值。 for i, v in enumerate([tic, tac, toe]):
... print(i, v)
...
0 tic
1 tac
2 toe为了同时循环两个或多个序列,条目可以与zip()函数配对。 questions [name, quest, favorite color]answers [lancelot, the holy grail, blue]for q, a in zip(questions, answers):
... print(What is your {0}? It is {1}..format(q, a))
...
What is your name? It is lancelot.
What is your quest? It is the holy grail.
What is your favorite color? It is blue.在反向的过程中循环,首先在正向方向上指定序列,然后调用reversed()函数。 for i in reversed(range(1, 10, 2)):
... print(i)
...
9
7
5
3
1要以排序的方式循环一个序列使用sorted() 函数返回一个新的排序列表,同时离开源未更改。 basket [apple, orange, apple, pear, orange, banana]for i in sorted(basket):
... print(i)
...
apple
apple
banana
orange
orange
pear在序列上使用set()可以消除重复元素。在序列上结合使用sorted()和set()是按排序顺序循环序列中唯一元素的惯用方法。 basket [apple, orange, apple, pear, orange, banana]for f in sorted(set(basket)):
... print(f)
...
apple
banana
orange
pear当你在列表上循环的时候有时改变列表是很诱人的;然而创建一个新列表通常更简单、更安全。 import mathraw_data [56.2, float(NaN), 51.7, 55.3, 52.5, float(NaN), 47.8]filtered_data []for value in raw_data:
... if not math.isnan(value):
... filtered_data.append(value)
...filtered_data
[56.2, 51.7, 55.3, 52.5, 47.8]5.7 更多关于条件
while和if语句中使用的条件可以包含任何操作符而不仅仅是比较。
比较操作符in和not in检查一个值是否在序列中出现(不出现)。操作符is和is not比较两个对象是否真的是同一个对象。所有比较操作符具有相同的优先级优先级低于所有数值操作符。
比较可以链式进行。例如a b c测试a是否小于b并且b是否等于c。
可以使用布尔运算符and和or组合比较并且可以用not来否定比较(或任何其他布尔表达式)的结果。它们的优先级低于比较操作符;在它们之间not具有最高优先级和or最低优先级因此A and not B or C等价于(A and (not B)) or C。一如既往括号可以用来表示期望的组合。
布尔运算符and和or是所谓的短路运算符:它们的参数从左到右求值一旦确定结果求值就停止。例如如果A和C为真但B为假则A and B and C不对表达式C求值。当用作通用值而不是布尔值时短路运算符的返回值是最后求值的参数。
可以将比较结果或其他布尔表达式赋值给变量。例如, string1, string2, string3 , Trondheim, Hammer Dancenon_null string1 or string2 or string3non_null
Trondheim请注意在Python中与C语言不同表达式内部的赋值必须使用海象操作符:显式地完成。这避免了C程序中遇到的一类常见问题:在打算使用时在表达式中键入。
5.8 比较序列和其他类型
序列对象通常可以与具有相同序列类型的其他对象进行比较。比较使用字典(lexicographical)顺序:首先比较前两个项如果它们不同则决定比较的结果;如果它们相等则比较接下来的两个项依此类推直到任意一个序列都用完。如果要比较的两个项本身是相同类型的序列则递归地进行字典比较。如果两个序列的所有项比较相等则认为这两个序列相等。如果一个序列是另一个序列的初始子序列则较短的序列是较小(较小)的序列。字符串的字典顺序使用Unicode代码点编号对单个字符进行排序。一些相同类型序列之间比较的例子:
(1, 2, 3) (1, 2, 4)
[1, 2, 3] [1, 2, 4]
ABC C Pascal Python
(1, 2, 3, 4) (1, 2, 4)
(1, 2) (1, 2, -1)
(1, 2, 3) (1.0, 2.0, 3.0)
(1, 2, (aa, ab)) (1, 2, (abc, a), 4)注意使用或比较不同类型的对象是合法的前提是对象具有适当的比较方法。例如混合数字类型根据其数值进行比较因此0等于0.0等等。否则解释器将引发TypeError异常而不是提供任意排序。
6、模块
如果您退出Python解释器并再次进入它您所做的定义(函数和变量)将丢失。因此如果您想编写较长的程序最好使用文本编辑器为解释器准备输入并将该文件作为输入运行解释器。这就是所谓的创建脚本(script)。随着程序变长您可能希望将其分成几个文件以便于维护。您可能还希望使用在几个程序中编写的方便函数而不将其定义复制到每个程序中。
为了支持这一点**Python有一种方法可以将定义放在文件中并在脚本或解释器的交互式实例中使用它们**。这样的文件称为模块(module);模块中的定义可以导入(imported)到其他模块或主(main)模块中(在顶层以计算器模式执行的脚本中可以访问的变量集合)。
模块是包含Python定义和语句的文件。文件名是附加了.py后缀的模块名。在模块中模块的名称(作为字符串)可以作为全局变量__name__的值使用。例如使用您最喜欢的文本编辑器在当前目录中创建一个名为fibo.py的文件其内容如下:
# Fibonacci numbers moduledef fib(n): # write Fibonacci series up to na, b 0, 1while a n:print(a, end )a, b b, abprint()def fib2(n): # return Fibonacci series up to nresult []a, b 0, 1while a n:result.append(a)a, b b, abreturn result现在进入Python解释器并用以下命令导入该模块: import fibo这不会将fibo中定义的函数名直接输入到当前符号表中;它只在那里输入模块名fibo。使用模块名你可以访问以下函数: fibo.fib(1000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987fibo.fib2(100)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]fibo.__name__
fibo如果你打算经常使用一个函数你可以给它赋一个本地名称: fib fibo.fibfib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 3776.1 更多关于模块的信息
模块可以包含可执行语句以及函数定义。这些语句用于初始化模块。它们只在import语句中第一次遇到模块名时执行。(如果文件作为脚本执行它们也会运行。)
每个模块都有自己的私有符号表它被模块中定义的所有函数用作全局符号表。因此模块的作者可以在模块中使用全局变量而不必担心与用户的全局变量发生意外冲突。另一方面如果你知道你在做什么你可以用与引用它的函数相同的符号modname.itemname来触摸模块的全局变量。
模块可以导入其他模块。习惯上将所有import语句放在模块(或脚本)的开头但这不是必需的。导入的模块名称放在导入模块的全局符号表中。
import语句有一个变体它将模块中的名称直接导入到导入模块的符号表中。例如: from fibo import fib, fib2fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377这并没有在局部符号表中引入模块名(因此在本例中没有定义fibo)。
甚至还有一个变体可以导入模块定义的所有名称: from fibo import *fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377这将导入所有名称但以下划线(_)开头的名称除外。在大多数情况下Python程序员不使用此功能因为它将一组未知的名称引入解释器可能会隐藏一些您已经定义的内容。
请注意通常不赞成从模块或包中导入*因为它经常导致代码可读性差。但是在交互式会话中使用它来节省打字是可以的。
如果模块名后面跟as那么as后面的名称直接绑定到导入的模块。 import fibo as fibfib.fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377这实际上是以导入fibo相同的方式导入模块唯一的区别是它可以作为fib使用。
它也可以在使用from时使用具有类似的效果: from fibo import fib as fibonaccifibonacci(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377注意:出于效率考虑每个模块在每个解释器会话中只导入一次。因此如果你改变了你的模块你必须重新启动解释器——或者如果你只想交互测试一个模块使用importlib.reload()例如import importlib; importlib.reload(modulename)。 6.1.1 将模块作为脚本执行
运行Python模块时
python fibo.py arguments模块中的代码将被执行就像您导入它一样但__name__设置为__main__。这意味着通过在模块末尾添加这段代码:
if __name__ __main__:import sysfib(int(sys.argv[1]))你可以使该文件既可用作脚本也可用作可导入的模块因为解析命令行的代码只有在模块作为“main”文件执行时才会运行:
$ python fibo.py 50
0 1 1 2 3 5 8 13 21 34如果导入了模块则不会运行代码: import fibo这通常用于为模块提供方便的用户界面或者用于测试目的(将模块作为脚本运行以执行测试套件)。
6.1.2 模块搜索路径
当导入名为spam的模块时解释器首先搜索具有该名称的内置模块。这些模块名称列在sys.builtin_module_names中。如果没有找到它就在变量sys.path给出的目录列表中搜索一个名为spam.py的文件。sys.path从这些位置初始化:
包含输入脚本的目录(如果没有指定文件则为当前目录)。PYTHONPATH(一个目录名列表语法与shell变量PATH相同)。依赖安装的默认值(按照约定包括site-packages目录由site模块处理)。 注意:在支持符号链接的文件系统上包含输入脚本的目录将在符号链接之后计算。换句话说包含符号链接的目录不会被添加到模块搜索路径中。 初始化后Python程序可以修改sys.path。包含正在运行的脚本的目录放在搜索路径的开头在标准库路径之前。这意味着将加载该目录中的脚本而不是加载库目录中同名的模块。这是一个错误除非替换是有意的。请参阅标准模块小节了解更多信息
6.1.3 “编译”的Python文件
为了加快加载模块的速度Python在__pycache__目录下以module.version.pyc的名称缓存每个模块的编译版本。其中version编码编译文件的格式;它通常包含Python版本号。例如在CPython 3.3版中spam.py编译后的版本会被缓存为__pycache__/spam.cpython-33.pyc。这种命名约定允许来自不同发行版和不同版本的Python的编译模块共存。
Python根据编译后的版本检查源代码的修改日期以查看它是否过期并需要重新编译。这是一个完全自动的过程。此外编译的模块是平台独立的因此相同的库可以在具有不同体系结构的系统之间共享。
Python在两种情况下不检查缓存。首先它总是重新编译并且不存储直接从命令行加载的模块的结果。其次如果没有源模块则不检查缓存。要支持非源(仅编译)发行版编译模块必须位于源目录中并且不能有源模块。
给专家的一些建议:
您可以使用Python命令上的-O或-OO开关来减小编译模块的大小。-O开关删除assert语句-OO开关删除assert语句和__doc__字符串。由于一些程序可能依赖于这些可用的选项所以只有在您知道自己在做什么的情况下才应该使用这个选项。“优化”模块有一个opt-标签通常更小。未来的版本可能会改变优化的效果。从.pyc文件中读取程序并不比从.py文件中读取程序运行得更快;.pyc文件唯一更快的地方是加载它们的速度。compileall模块可以为一个目录中的所有模块创建.pyc文件。PEP 3147中有关于这个过程的更多细节包括决策流程图。
6.2 标准模块
Python附带了一个标准模块库在单独的文档Python库参考(Python Library Reference以下简称“库参考”)中进行了描述。有些模块内置于解释器中;它们提供了对不属于语言核心的操作的访问但这些操作是内建的要么是为了提高效率要么是为了提供对操作系统原语(如系统调用)的访问。这些模块的集合是一个配置选项它也依赖于底层平台。例如winreg模块仅在Windows系统上提供。有一个特别的模块值得注意:sys它内置于每个Python解释器中。变量sys.ps1和sys.ps2定义了用于主提示符和辅助提示符的字符串: import syssys.ps1sys.ps2
... sys.ps1 C
C print(Yuck!)
Yuck!
C只有在解释器处于交互模式时才定义这两个变量。
变量sys.path是一个字符串列表用于确定解释器对模块的搜索路径。它被初始化为从环境变量PYTHONPATH获取的默认路径如果未设置PYTHONPATH则从内置默认路径初始化。你可以使用标准的列表操作来修改它: import syssys.path.append(/ufs/guido/lib/python)6.3 dir()函数
内置函数dir()用于查找模块定义了哪些名称。它返回一个有序的字符串列表: import fibo, sysdir(fibo)
[__name__, fib, fib2]dir(sys)
[__breakpointhook__, __displayhook__, __doc__, __excepthook__,__interactivehook__, __loader__, __name__, __package__, __spec__,__stderr__, __stdin__, __stdout__, __unraisablehook__,_clear_type_cache, _current_frames, _debugmallocstats, _framework,_getframe, _git, _home, _xoptions, abiflags, addaudithook,api_version, argv, audit, base_exec_prefix, base_prefix,breakpointhook, builtin_module_names, byteorder, call_tracing,callstats, copyright, displayhook, dont_write_bytecode, exc_info,excepthook, exec_prefix, executable, exit, flags, float_info,float_repr_style, get_asyncgen_hooks, get_coroutine_origin_tracking_depth,getallocatedblocks, getdefaultencoding, getdlopenflags,getfilesystemencodeerrors, getfilesystemencoding, getprofile,getrecursionlimit, getrefcount, getsizeof, getswitchinterval,gettrace, hash_info, hexversion, implementation, int_info,intern, is_finalizing, last_traceback, last_type, last_value,maxsize, maxunicode, meta_path, modules, path, path_hooks,path_importer_cache, platform, prefix, ps1, ps2, pycache_prefix,set_asyncgen_hooks, set_coroutine_origin_tracking_depth, setdlopenflags,setprofile, setrecursionlimit, setswitchinterval, settrace, stderr,stdin, stdout, thread_info, unraisablehook, version, version_info,warnoptions]如果不带参数dir()会列出你当前定义的名称: a [1, 2, 3, 4, 5]import fibofib fibo.fibdir()
[__builtins__, __name__, a, fib, fibo, sys]请注意它列出了所有类型的名称:变量、模块、函数等。
dir()不列出内置函数和变量的名称。如果你想要它们的列表它们在标准模块builtins中定义: import builtinsdir(builtins)
[ArithmeticError, AssertionError, AttributeError, BaseException,BlockingIOError, BrokenPipeError, BufferError, BytesWarning,ChildProcessError, ConnectionAbortedError, ConnectionError,ConnectionRefusedError, ConnectionResetError, DeprecationWarning,EOFError, Ellipsis, EnvironmentError, Exception, False,FileExistsError, FileNotFoundError, FloatingPointError,FutureWarning, GeneratorExit, IOError, ImportError,ImportWarning, IndentationError, IndexError, InterruptedError,IsADirectoryError, KeyError, KeyboardInterrupt, LookupError,MemoryError, NameError, None, NotADirectoryError, NotImplemented,NotImplementedError, OSError, OverflowError,PendingDeprecationWarning, PermissionError, ProcessLookupError,ReferenceError, ResourceWarning, RuntimeError, RuntimeWarning,StopIteration, SyntaxError, SyntaxWarning, SystemError,SystemExit, TabError, TimeoutError, True, TypeError,UnboundLocalError, UnicodeDecodeError, UnicodeEncodeError,UnicodeError, UnicodeTranslateError, UnicodeWarning, UserWarning,ValueError, Warning, ZeroDivisionError, _, __build_class__,__debug__, __doc__, __import__, __name__, __package__, abs,all, any, ascii, bin, bool, bytearray, bytes, callable,chr, classmethod, compile, complex, copyright, credits,delattr, dict, dir, divmod, enumerate, eval, exec, exit,filter, float, format, frozenset, getattr, globals, hasattr,hash, help, hex, id, input, int, isinstance, issubclass,iter, len, license, list, locals, map, max, memoryview,min, next, object, oct, open, ord, pow, print, property,quit, range, repr, reversed, round, set, setattr, slice,sorted, staticmethod, str, sum, super, tuple, type, vars,zip]6.4 包 (Packages)
包是通过使用“带点的模块名”来构建Python模块命名空间的一种方式。例如模块名A.B在名为A的包中指定了一个名为B的子模块。就像使用模块名可以让不同模块的作者不必担心彼此的全局变量名一样使用点模块名可以让NumPy或Pillow等多模块包的作者不必担心彼此的模块名。
假设您想要设计一个模块集合(一个“包”)来统一处理声音文件和声音数据。有许多不同的声音文件格式(通常通过它们的扩展名识别例如:.wav、.aiff、.au)因此您可能需要创建和维护一个不断增长的模块集合以便在各种文件格式之间进行转换。您可能还希望对声音数据执行许多不同的操作(例如混音、添加回声、应用均衡器函数、创建人工立体声效果)因此您将编写一个永无止境的模块流来执行这些操作。这是你的包可能的结构(用分层文件系统表示):
sound/ Top-level package__init__.py Initialize the sound packageformats/ Subpackage for file format conversions__init__.pywavread.pywavwrite.pyaiffread.pyaiffwrite.pyauread.pyauwrite.py...effects/ Subpackage for sound effects__init__.pyecho.pysurround.pyreverse.py...filters/ Subpackage for filters__init__.pyequalizer.pyvocoder.pykaraoke.py...导入包时Python会在sys. path上的目录中搜索。查找包子目录的路径。
需要__init__.py文件才能使Python将包含该文件的目录视为包。这可以防止具有公共名称(如string)的目录无意中隐藏稍后在模块搜索路径上出现的有效模块。在最简单的情况下__init__.py可以只是一个空文件但它也可以执行包的初始化代码或设置__all__变量稍后会介绍。
包的用户可以从包中导入单个模块例如:
import sound.effects.echo这将加载sound.effects.echo子模块。必须使用其全名来引用。
sound.effects.echo.echofilter(input, output, delay0.7, atten4)另一种导入子模块的方法是:
from sound.effects import echo这也会加载子模块echo并使它在没有包前缀的情况下可用所以它可以这样使用:
echo.echofilter(input, output, delay0.7, atten4)另一种变体是直接导入所需的函数或变量:
from sound.effects.echo import echofilter同样这会加载子模块echo但这会使其函数echofilter()直接可用:
echofilter(input, output, delay0.7, atten4)请注意当使用from package import item时item可以是包的子模块(或子包)也可以是包中定义的其他名称如函数、类或变量。import语句首先测试项是否在包中定义;如果不是它就假定它是一个模块并尝试加载它。如果找不到就会引发ImportError异常。
相反当使用import item.subitem.subsubitem。除最后一项外每一项必须是一个包;最后一项可以是模块或包但不能是前一项中定义的类、函数或变量。
6.4.1 从包中导入*
现在当用户写入from sound.effects import *时会发生什么呢?理想情况下我们希望它以某种方式进入文件系统找到包中存在的子模块并导入它们。这可能会花费很长时间并且导入子模块可能会产生不必要的副作用这种副作用只有在显式导入子模块时才会发生。
唯一的解决方案是包作者提供包的显式索引。import语句使用以下约定:如果一个包的__init__.py代码定义了一个名为__all__的列表它被认为是当遇到from package import * 时应该导入的模块名列表。当包的新版本发布时由包的作者来保持这个列表是最新的。如果包的作者不认为从包中导入*有什么用他们也可能决定不支持它。例如文件sound/effects/__init__.pY可能包含以下代码:
__all__ [echo, surround, reverse]这意味着from sound.effects import * 将导入sound.effects的三个命名子模块。
如果未定义__all__则语句rom sound.effects import * 不会从包sound.effects中导入所有子模块到导入当前命名空间;它只保证sound.effects已经被导入(可能在__init__.py中运行任何初始化代码)然后导入包中定义的任何名称。这包括__init__.py定义的任何名字(以及显式加载的子模块)。它还包括由前面的import语句显式加载的包的任何子模块。考虑这段代码:
import sound.effects.echo
import sound.effects.surround
from sound.effects import *在本例中echo和surround模块被导入到当前名称空间中因为它们是在sound.effects中定义的。当执行from...import语句。(这在定义__all__时也有效。)
尽管在使用import *时某些模块被设计为只导出遵循特定模式的名称但在生产代码中这仍然被认为是不好的做法。
记住使用from package import specific_submodule并没有错!事实上这是推荐的符号除非导入模块需要使用来自不同包的同名子模块。
6.4.2 Intra-package引用
当包被构造成子包时(如示例中的sound包)您可以使用绝对导入来引用兄弟包的子模块。例如如果模块sound.filters.vocoder需要在sound.effects中使用echo模块。可以使用from sound.effects import echo。
您还可以使用import语句的from module import name形式编写相对导入。这些导入使用前导点(leading dots)来指示相对导入中涉及的当前包和父包。例如在surround模块中你可以使用:
from . import echo
from .. import formats
from ..filters import equalizer注意相对导入是基于当前模块的名称。由于主模块的名称始终为__main__因此打算用作Python应用程序主模块的模块必须始终使用绝对导入。
6.4.3 多个目录下的包
包支持一个特殊的属性path在执行该文件中的代码之前它被初始化为一个包含保存包的__init__.py目录名称的列表。这个变量可以修改;这样做会影响将来对包中包含的模块和子包的搜索。
虽然不经常需要这个特性但它可以用于扩展包中的模块集。