Python 基础 -- Tutorial(二)

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

可选参数startend被解释为切片表示法,并用于将搜索限制为列表的特定子序列。返回的索引是相对于整个序列的开头计算的,而不是相对于start参数计算的。

list.count(x)
返回x在列表中出现的次数。

list.sort(*, key=None, reverse=False)
对列表中的项进行排序(参数可用于自定义排序,请参阅sorted()了解其解释)。

list.reverse()
将列表中的元素反转。

list.copy()
返回列表的浅拷贝。相当于a[:]

一个使用了大多数列表方法的例子:

>>> fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']
>>> fruits.count('apple')
2
>>> fruits.count('tangerine')
0
>>> fruits.index('banana')
3
>>> fruits.index('banana', 4)  # Find next banana starting a position 4
6
>>> fruits.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, removesort这样只修改列表的方法不会打印返回值-它们返回默认值None。这是Python中所有可变数据结构的设计原则。

您可能会注意到的另一件事是,并非所有数据都可以排序或比较。例如,[None, ‘hello’, 10]不排序,因为整数不能与字符串比较,None不能与其他类型比较。此外,还有一些类型没有定义排序关系。例如,3+4j < 5+7j不是一个有效的比较。

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子句,然后是零个或多个forif子句。结果将是在其后的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)]

注意,在这两个代码片段中,forif语句的顺序是相同的。

如果表达式是一个元组(例如前面示例中的(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 listcomp
    transposed_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 comma
>>> len(empty)
0
>>> len(singleton)
1
>>> singleton
('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
True
>>> 'crabgrass' 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'] = 4127
>>> tel
{
    
    'jack': 4098, 'sape': 4139, 'guido': 4127}
>>> tel['jack']
4098
>>> del tel['sape']
>>> tel['irv'] = 4127
>>> tel
{
    
    'jack': 4098, 'guido': 4127, 'irv': 4127}
>>> list(tel)
['jack', 'guido', 'irv']
>>> sorted(tel)
['guido', 'irv', 'jack']
>>> 'guido' in tel
True
>>> 'jack' not in tel
False

dict()构造函数直接从键值对序列构建字典:

>>> 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(sape=4139, guido=4127, jack=4098)
{
    
    '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 math
>>> raw_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 更多关于条件

whileif语句中使用的条件可以包含任何操作符,而不仅仅是比较。

比较操作符innot in检查一个值是否在序列中出现(不出现)。操作符isis not比较两个对象是否真的是同一个对象所有比较操作符具有相同的优先级,优先级低于所有数值操作符

比较可以链式进行。例如,a < b == c测试a是否小于b,并且b是否等于c

可以使用布尔运算符andor组合比较,并且可以用not来否定比较(或任何其他布尔表达式)的结果它们的优先级低于比较操作符;在它们之间,not具有最高优先级和or最低优先级,因此A and not B or C等价于(A and (not B)) or C。一如既往,括号可以用来表示期望的组合

布尔运算符andor是所谓的短路运算符:它们的参数从左到右求值,一旦确定结果,求值就停止。例如,如果A和C为真,但B为假,则A and B and C不对表达式C求值。当用作通用值而不是布尔值时,短路运算符的返回值是最后求值的参数。

可以将比较结果或其他布尔表达式赋值给变量。例如,

>>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
>>> non_null = string1 or string2 or string3
>>> non_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 module

def fib(n):    # write Fibonacci series up to n
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a+b
    print()

def fib2(n):   # return Fibonacci series up to n
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)
        a, b = b, a+b
    return 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 987
>>> fibo.fib2(100)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> fibo.__name__
'fibo'

如果你打算经常使用一个函数,你可以给它赋一个本地名称:

>>> fib = fibo.fib
>>> fib(500)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

6.1 更多关于模块的信息

模块可以包含可执行语句以及函数定义这些语句用于初始化模块。它们只在import语句中第一次遇到模块名时执行。(如果文件作为脚本执行,它们也会运行。)

每个模块都有自己的私有符号表,它被模块中定义的所有函数用作全局符号表。因此,模块的作者可以在模块中使用全局变量,而不必担心与用户的全局变量发生意外冲突。另一方面,如果你知道你在做什么,你可以用与引用它的函数相同的符号modname.itemname来触摸模块的全局变量

模块可以导入其他模块。习惯上将所有import语句放在模块(或脚本)的开头,但这不是必需的。导入的模块名称放在导入模块的全局符号表中。

import语句有一个变体,它将模块中的名称直接导入到导入模块的符号表中。例如:

>>> from fibo import fib, fib2
>>> fib(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 fib
>>> fib.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 fibonacci
>>> fibonacci(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 sys
    fib(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.ps1sys.ps2定义了用于主提示符和辅助提示符的字符串:

>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Yuck!')
Yuck!
C>

只有在解释器处于交互模式时才定义这两个变量

变量sys.path是一个字符串列表,用于确定解释器对模块的搜索路径。它被初始化为从环境变量PYTHONPATH获取的默认路径,如果未设置PYTHONPATH,则从内置默认路径初始化。你可以使用标准的列表操作来修改它:

>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')

6.3 dir()函数

内置函数dir()用于查找模块定义了哪些名称。它返回一个有序的字符串列表:

>>> import fibo, sys
>>> dir(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 fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']

请注意,它列出了所有类型的名称:变量、模块、函数等

dir()不列出内置函数和变量的名称。如果你想要它们的列表,它们在标准模块builtins中定义:

>>> import builtins
>>> dir(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 package
      formats/                  Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  Subpackage for sound effects
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  Subpackage for filters
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

导入包时,Python会在sys. path上的目录中搜索。查找包子目录的路径。

需要__init__.py文件才能使Python将包含该文件的目录视为包。这可以防止具有公共名称(如string)的目录无意中隐藏稍后在模块搜索路径上出现的有效模块。在最简单的情况下,__init__.py可以只是一个空文件,但它也可以执行包的初始化代码或设置__all__变量,稍后会介绍。

包的用户可以从包中导入单个模块,例如:

import sound.effects.echo

这将加载sound.effects.echo子模块。必须使用其全名来引用

sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

另一种导入子模块的方法是:

from sound.effects import echo

这也会加载子模块echo,并使它在没有包前缀的情况下可用,所以它可以这样使用:

echo.echofilter(input, output, delay=0.7, atten=4)

另一种变体是直接导入所需的函数或变量:

from sound.effects.echo import echofilter

同样,这会加载子模块echo,但这会使其函数echofilter()直接可用:

echofilter(input, output, delay=0.7, atten=4)

请注意,当使用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 *

在本例中,echosurround模块被导入到当前名称空间中,因为它们是在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目录名称的列表。这个变量可以修改;这样做会影响将来对包中包含的模块和子包的搜索。

虽然不经常需要这个特性,但它可以用于扩展包中的模块集。

猜你喜欢

转载自blog.csdn.net/chinusyan/article/details/132206225