• 主页
  • 相册
  • 随笔
  • 目录
  • 存档
Total 244
Search AboutMe

  • 主页
  • 相册
  • 随笔
  • 目录
  • 存档

Python入门-2

2020-01-24

1. 模块

  1. 如果你从Python解释器退出并再次进入,之前的定义(函数和变量)都会丢失。因此,如果你想编写一个稍长些的程序,最好使用文本编辑器为解释器准备输入并将该文件作为输入运行。这被称作编写 脚本 。

  2. 随着程序变得越来越长,你或许会想把它拆分成几个文件,以方便维护。你亦或想在不同的程序中使用一个便捷的函数, 而不必把这个函数复制到每一个程序中去。为支持这些,Python有一种方法可以把定义放在一个文件里,并在脚本或解释器的交互式实例中使用它们。这样的文件被称作 模块

  3. 模块是一个包含Python定义和语句的文件。文件名就是模块名后跟文件后缀 .py 。在一个模块内部,模块名(作为一个字符串)可以通过全局变量 __name__ 的值获得

  4. 模块可以包含可执行的语句以及函数定义。这些语句用于初始化模块。它们仅在模块 第一次 在 import 语句中被导入时才执行。

  5. 每个模块都有它自己的私有符号表,该表用作模块中定义的所有函数的全局符号表。因此,模块的作者可以在模块内使用全局变量,而不必担心与用户的全局变量发生意外冲突。另一方面,如果你知道自己在做什么,则可以用跟访问模块内的函数的同样标记方法,去访问一个模块的全局变量,modname.itemname。

  6. 模块可以导入其它模块。习惯上但不要求把所有import语句放在模块(或脚本)的开头。

  7. 变体,

    1. 可以把名字从一个被调模块内直接导入到现模块的符号表里

      1
      2
      3
      >>> from fibo import fib, fib2
      >>> fib(500)
      0 1 1 2 3 5 8 13 21 34 55 89 144 233 377

      这并不会把被调模块名引入到局部变量表里(因此在这个例子里,fibo 是未被定义的)

    2. 还有一个变体甚至可以导入模块内定义的所有名称

      1
      2
      >>> from fibo import *
      >>> fib(500)
    3. 如果模块名称之后带有 as,则跟在 as 之后的名称将直接绑定到所导入的模块。

      1
      2
      3
      4
      >>> import fibo as fib
      >>> fib.fib(500)

      # >>> from fibo import fib as fibonacci

    出于效率的考虑,每个模块在每个解释器会话中只被导入一次。因此,如果你更改了你的模块,则必须重新启动解释器, 或者,如果它只是一个要交互式地测试的模块,请使用importlib.release()例如 import importlib; importlib.reload(modulename)。

2. 以脚本的方式

1
python fibo.py <arguments>

模块里的代码会被执行,就好像你导入了模块一样,但是 __name__ 被赋值为 "__main__"。 这意味着通过在你的模块末尾添加这些代码:

1
2
3
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))

你既可以把这个文件当作脚本又可当作一个可调入的模块来使用, 因为那段解析命令行的代码只有在当模块是以“main”文件的方式执行的时候才会运行:

如果模块是被导入的,那些代码是不运行的:

1
2
>>> import fibo
>>>

2.1. 模块路径

当一个名为 spam 的模块被导入的时候,解释器首先寻找具有该名称的内置模块。如果没有找到,然后解释器从 sys.path 变量给出的目录列表里寻找名为 spam.py 的文件。

2.2. 编译后

为了加速模块载入,Python在 __pycache__ 目录里缓存了每个模块的编译后版本,名称为 module.*version*.pyc ,其中名称中的版本字段对编译文件的格式进行编码; 它一般使用Python版本号。例如,在CPython版本3.3中,spam.py的编译版本将被缓存为 __pycache__/spam.cpython-33.pyc。

Python根据编译版本检查源的修改日期,以查看它是否已过期并需要重新编译。这是一个完全自动化的过程。

Python在两种情况下不会检查缓存。首先,对于从命令行直接载入的模块,它从来都是重新编译并且不存储编译结果;其次,如果没有源模块,它不会检查缓存。

3. dir()

列出所有类型的名称:变量,模块,函数,等等。

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

1
2
>>> import builtins
>>> dir(builtins)

如果没有参数,dir()列出你当前定义的名称

1
2
3
4
5
>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']

4. 包

包是个一种模块集合,一种通过用“带点号的模块名”来构造 Python 模块命名空间的方法。 例如,模块名 A.B 表示 A 包中名为 B 的子模块。

包结构示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 里的目录,查找包的子目录。

sys.path初始有这些目录地址:

  • 包含输入脚本的目录(或者未指定文件时的当前目录)。
  • PATHONPATH(一个包含目录名称的列表,它和shell变量 PATH 有一样的语法)。
  • 取决于安装的默认设置

必须要有 __init__.py 文件才能让 Python 将包含该文件的目录当作包。 这样可以防止具有通常名称例如 string 的目录在无意中隐藏稍后在模块搜索路径上出现的有效模块。 在最简单的情况下,__init__.py 可以只是一个空文件,但它也可以执行包的初始化代码或设置 __all__ 变量。

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

    1
    import sound.effects.echo

    这会加载子模块 sound.effects.echo 。但引用它时必须使用它的全名。

    1
    sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)
  2. 导入子模块的另一种方法是

    1
    from sound.effects import echo

    这也会加载子模块 echo ,并使其在没有包前缀的情况下可用,因此可以按如下方式使用:

    1
    echo.echofilter(input, output, delay=0.7, atten=4)
  3. 另一种形式是直接导入所需的函数或变量:

    1
    from sound.effects.echo import echofilter

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

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

    请注意,当使用 from package import item 时,item可以是包的子模块(或子包),也可以是包中定义的其他名称,如函数,类或变量。 import 语句首先测试是否在包中定义了item;如果没有,它假定它是一个模块并尝试加载它。如果找不到它,则引发ImportError异常。

    相反,当使用 import item.subitem.subsubitem 这样的语法时,除了最后一项之外的每一项都必须是一个包;最后一项可以是模块或包,但不能是前一项中定义的类或函数或变量

5. Import *

import语句使用下面的规范:如果一个包的 __init__.py 代码定义了一个名为 __all__ 的列表,它会被视为在遇到 from package import * 时应该导入的模块名列表。在发布该包的新版本时,包作者可以决定是否让此列表保持更新

包作者如果认为从他们的包中导入 * 的操作没有必要被使用,也可以决定不支持此列表。例如,文件 sound/effects/__init__.py 可以包含以下代码:

1
__all__ = ["echo", "surround", "reverse"]

这意味着 from sound.effects import * 将导入 sound 包的三个命名子模块。

如果没有定义 __all__,from sound.effects import * 语句 不 会从包 sound.effects 中导入所有子模块到当前命名空间;它只确保导入了包 sound.effects (可能运行任何在 __init__.py 中的初始化代码),然后导入包中定义的任何名称。

使用 from package import specific_submodule 没有任何问题! 实际上,除非导入的模块需要使用来自不同包的同名子模块,否则这是推荐的表示法。

6. 子包

你还可以使用import语句的 from module import name 形式编写相对导入。这些导入使用前导点来指示相对导入中涉及的当前包和父包。例如,从 surround 模块,你可以使用:

1
2
3
from . import echo
from .. import formats
from ..filters import equalizer

7. 输入输出

8. 格式化输出

  • 格式字字符串字面值,请在字符串的开始引号或三引号之前加上一个 f 或 F 。在此字符串中,你可以在 { 和 } 字符之间写可以引用的变量或字面值的 Python 表达式。

    1
    2
    3
    4
    >>> year = 2016
    >>> event = 'Referendum'
    >>> f'Results of the {year} {event}'
    'Results of the 2016 Referendum'
  • 字符串的str.format()方法需要更多的手动操作。你仍将使用 { 和 } 来标记变量将被替换的位置,并且可以提供详细的格式化指令,但你还需要提供要格式化的信息

    1
    2
    >>> '{:-9} YES votes  {:2.2%}'.format(yes_votes, percentage)
    ' 42572654 YES votes 49.67%'

    花括号中的数字可用来表示传递给str.format()方法的对象的位置。

    1
    2
    3
    4
    >>> print('{0} and {1}'.format('spam', 'eggs'))
    spam and eggs
    >>> print('{1} and {0}'.format('spam', 'eggs'))
    eggs and spam

    使用关键字参数,则使用参数的名称引用它们的值。

    1
    2
    3
    4
    5
    6
    7
    8
    >>> print('This {food} is {adjective}.'.format(
    ... food='spam', adjective='absolutely horrible'))
    This spam is absolutely horrible.

    # 还能合起来用
    >>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred',
    other='Georg'))
    The story of Bill, Manfred, and Georg.

    这也可以通过使用 ‘**’ 符号将表作为关键字参数传递。(当作特别例子吧..从解包分析不出来)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    >>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
    >>> print('Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}'.format(**table))
    Jack: 4098; Sjoerd: 4127; Dcab: 8637678

    # another
    >>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}
    >>> print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '
    ... 'Dcab: {0[Dcab]:d}'.format(table))
    Jack: 4098; Sjoerd: 4127; Dcab: 8637678
  • 你可以使用字符串切片和连接操作自己完成所有的字符串处理,以创建你可以想象的任何布局。

当你不需要花哨的输出而只是想快速显示某些变量以进行调试时:

  • str():

    返回人类可读的值的表示

  • repr():

    生成解释器可读的表示

  • 对于没有人类可读性的表示的对象,str()将返回和 repr()一样的值。

string模块包含一个 Template类,它提供了另一种将值替换为字符串的方法,使用类似 $x 的占位符并用字典中的值替换它们,但对格式的控制要少的多。

1
2
3
4
>>>hello = 'hello, world\n'
>>> hellos = repr(hello)
>>> print(hellos)
'hello, world\n'

9. 手动格式化字符串

字符串对象的str.rjust()方法通过在左侧填充空格来对给定宽度的字段中的字符串进行右对齐。类似的方法还有str.ljust()和str.center()。

9.1. 旧式

1
2
3
>>> import math
>>> print('The value of pi is approximately %5.3f.' % math.pi)
The value of pi is approximately 3.142.

10. 读写文件

open:

  • 在处理文件对象时,最好使用with关键字。 优点是当子句体结束后文件会正确关闭,即使在某个时刻引发了异常。 而且使用 with 相比等效的try-finally代码块要简短得多:

    1
    2
    3
    4
    >>> with open('workfile') as f:
    ... read_data = f.read()
    >>> f.closed # 不是close()
    True
  • 如果你没有使用with关键字,那么你应该调用 f.close() 来关闭文件并立即释放它使用的所有系统资源。如果你没有显式地关闭文件,Python的垃圾回收器最终将销毁该对象并为你关闭打开的文件,但这个文件可能会保持打开状态一段时间。

f.read():

  • 要读取文件的内容,请调用f.read (size),它读取一些数据并以字符串(文本模式)或字节对象(二进制模式)的形式返回。size是一个可选的数值参数。当大小被省略或为负数时,将读取并返回文件的全部内容。否则,最多读取和返回大小字符(在文本模式下)或大小字节(在二进制模式下)。如果已经到达文件的末尾,f.read()将返回一个空字符串(“)。

f.readline():

1
2
3
4
>>> f.readline()
'Second line of the file\n'
>>> f.readline()
''

要从文件中读取行,你可以循环遍历文件对象。这是内存高效,快速的,并简化代码:

1
2
>>> for line in f:
... print(line, end='')

如果你想以列表的形式读取文件中的所有行,你也可以使用 list(f) 或 f.readlines()。

f.write():

  • f.write(string) 会把 string 的内容写入到文件中,并返回写入的字符数。:

    1
    2
    >>> f.write('This is a test\n')
    15

    在写入其他类型的对象之前,需要先把它们转化为字符串(在文本模式下)或者字节对象(在二进制模式下)

f.tell()

f.seek()

11. json

12. 错误与异常

13. 语法错误

解析器会输出出现语法错误的那一行,并显示一个“箭头”,指向这行里面检测到第一个错误。

1
2
3
4
  File "<stdin>", line 1
while True print('Hello world')
^
SyntaxError: invalid syntax

14. 异常

在执行时检测到的错误被称为异常

作为异常类型打印的字符串是发生的内置异常的名称。对于所有内置异常都是如此,但对于用户定义的异常则不一定如此(虽然这是一个有用的规范)。标准的异常类型是内置的标识符(而不是保留关键字)。

14.1. 处理异常

可以编写处理所选异常的程序。下面的例子,它会要求用户一直输入,直到输入的是一个有效的整数,但允许用户中断程序(使用 Control-C 或操作系统支持的其他操作);请注意用户引起的中断可以通过引发 KeyboardInterrupt异常来指示。:

1
2
3
4
5
6
>>> while True:
... try:
... x = int(input("Please enter a number: "))
... break
... except ValueError:
... print("Oops! That was no valid number. Try again...")

try语句的工作原理如下。

  • 首先,执行 try 子句 (try和 except关键字之间的(多行)语句)。

  • 如果没有异常发生,则跳过 except 子句 并完成try语句的执行。

  • 如果在执行try 子句时发生了异常,则跳过该子句中剩下的部分。然后,如果异常的类型和except关键字后面的异常匹配,则执行 except 子句 ,然后继续执行try语句之后的代码。

  • 如果发生的异常和 except 子句中指定的异常不匹配,则将其传递到外部的try语句中;如果没有找到处理程序,则它是一个 未处理异常,执行将停止并显示如上所示的消息。

    一个 except 子句可以将多个异常命名为带括号的元组,例如:

1
2
... except (RuntimeError, TypeError, NameError):
... pass

如果发生的异常和except子句中的类是同一个类或者是它的基类,则异常和except子句中的类是兼容的。例如,下面的代码将依次打印 B, C, D:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class B(Exception):
pass

class C(B):
pass

class D(C):
pass

for cls in [B, C, D]:
try:
raise cls()
except D:
print("D")
except C:
print("C")
except B:
print("B")

请注意如果 except 子句被颠倒(把 except B 放到第一个),它将打印 B,B,B。

最后的 except 子句可以省略异常名,以用作通配符。但请谨慎使用,因为以这种方式很容易掩盖真正的编程错误!它还可用于打印错误消息,然后重新引发异常(有点像default)。

  • 有一个可选的 else 子句,在使用时必须放在所有的 except 子句后面。对于在try 子句不引发异常时必须执行的代码来说很有用。例如

    1
    2
    3
    4
    5
    6
    7
    8
    for arg in sys.argv[1:]:
    try:
    f = open(arg, 'r')
    except OSError:
    print('cannot open', arg)
    else:
    print(arg, 'has', len(f.readlines()), 'lines')
    f.close()

    使用 else 子句比向 try子句添加额外的代码要好,因为它避免了意外捕获由 try … except 语句保护的代码未引发的异常。

  • 有另一个可选子句,用于定义必须在所有情况下执行的清理操作。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    >>> try:
    ... raise KeyboardInterrupt
    ... finally:
    ... print('Goodbye, world!')
    ...
    Goodbye, world!
    KeyboardInterrupt
    Traceback (most recent call last):
    File "<stdin>", line 2, in <module>

    如果出现了finally子句,那么finally子句将作为try语句完成之前的最后一个任务执行。不管try语句是否产生异常,finally子句都会运行。以下几点讨论发生异常时更复杂的情况:

    • 如果在执行try子句期间发生异常,则可以使用except子句处理异常。在所有情况下,在执行finally子句之后都会重新引发异常。

    • 在执行except或else子句时可能发生异常。同样,异常在finally子句执行之后重新引发。

    • 如果try语句到达break、continue或return语句,那么finally子句将恰好在break、continue或return语句执行之前执行。

    • 如果一个finally子句包含一个返回语句,那么finally子句的返回语句将在try子句中执行,而不是在return语句之前执行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    >>> def divide(x, y):
    ... try:
    ... result = x / y
    ... except ZeroDivisionError:
    ... print("division by zero!")
    ... else:
    ... print("result is", result)
    ... finally:
    ... print("executing finally clause")
    ...
    >>> divide(2, 1)
    result is 2.0
    executing finally clause
    >>> divide(2, 0)
    division by zero!
    executing finally clause
    >>> divide("2", "1")
    executing finally clause
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", line 3, in divide
    TypeError: unsupported operand type(s) for /: 'str' and 'str'

    finally 子句在任何情况下都会被执行。 两个字符串相除所引发的 TypeError 不会由 except 子句处理,因此会在 finally 子句执行后被重新引发。在实际应用程序中,finally 子句对于释放外部资源(例如文件或者网络连接)非常有用,无论是否成功使用资源。

except 子句可以在异常名称后面指定一个变量。这个变量和一个异常实例绑定,它的参数存储在 instance.args 中。为了方便起见,异常实例定义了__str()__,因此可以直接打印参数而无需引用 .args。也可以在抛出之前首先实例化异常,并根据需要向其添加任何属性。:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> try:
... raise Exception('spam', 'eggs')
... except Exception as inst:
... print(type(inst)) # the exception instance
... print(inst.args) # arguments stored in .args
... print(inst) # __str__ allows args to be printed directly,
... # but may be overridden in exception subclasses
... x, y = inst.args # unpack args
... print('x =', x)
... print('y =', y)
...
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

异常处理程序不仅处理 try 子句中遇到的异常,还处理 try 子句中调用(即使是间接地)的函数内部发生的异常。例如:

1
2
3
4
5
6
7
8
9
>>> def this_fails():
... x = 1/0
...
>>> try:
... this_fails()
... except ZeroDivisionError as err:
... print('Handling run-time error:', err)
...
Handling run-time error: division by zero

15. 抛出异常

raise 语句允许程序员强制发生指定的异常。例如:

1
2
3
4
>>> raise NameError('HiThere')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: HiThere

唯一的参数就是要抛出的异常。这个参数必须是一个异常实例或者是一个异常类(派生自Exception的类)。如果传递的是一个异常类,它将通过调用没有参数的构造函数来隐式实例化:

1
raise ValueError  # shorthand for 'raise ValueError()'

如果你需要确定是否引发了异常但不打算处理它,则可以使用更简单的raise语句形式重新引发异常

1
2
3
4
5
6
7
8
9
10
>>> try:
... raise NameError('HiThere')
... except NameError:
... print('An exception flew by!')
... raise
...
An exception flew by!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
NameError: HiThere

16. 用户自定义异常

程序可以通过创建新的异常类来命名它们自己的异常。异常通常应该直接或间接地从Exception类派生。

17. 预定义的清理操作

1
2
for line in open("myfile.txt"):
print(line, end="")

这个代码的问题在于,它在这部分代码执行完后,会使文件在一段不确定的时间内处于打开状态。而用with,执行完语句后,即使在处理行时遇到问题,文件 f 也始终会被关闭。

18. 类

类提供了一种组合数据和功能的方法。创建一个新类意味着创建一个新 类型 的对象,从而允许创建一个该类型的新 实例 。

Python 的类提供了面向对象编程的所有标准特性:类继承机制允许多个基类,派生类可以覆盖它基类的任何方法,一个方法可以调用基类中相同名称的的方法。对象可以包含任意数量和类型的数据。和模块一样,类也拥有 Python 天然的动态特性:它们在运行时创建,可以在创建后修改。

19. 命名空间

  • 从名字到对象的映射

  • 关于命名空间的重要一点是,不同命名空间中的名称之间绝对没有关系;例如,两个不同的模块都可以定义一个 maximize 函数而不会产生混淆 — 模块的用户必须在其前面加上模块名称。

  • 顺便说明一下,我把任何跟在一个点号之后的名称都称为 属性 — 例如,在表达式 z.real 中,real 是对象 z 的一个属性。按严格的说法,对模块中名称的引用属于属性引用:在表达式 modname.funcname 中,modname 是一个模块对象而 funcname 是它的一个属性。在此情况下在模块的属性和模块中定义的全局名称之间正好存在一个直观的映射:它们共享相同的命名空间

    • 属性可以是只读或者可写的。可写的属性同样可以用del语句删除。
  • 一个 作用域 是一个命名空间可直接访问的 Python 程序的文本区域。 这里的 “可直接访问” 意味着对名称的非限定引用会尝试在命名空间中查找名称。

    • 最先搜索的最内部作用域包含局部名称
    • 从最近的封闭作用域开始搜索的任何封闭函数的范围包含非局部名称,也包括非全局名称
    • 倒数第二个作用域包含当前模块的全局名称
    • 最外面的范围(最后搜索)是包含内置名称的命名空间
  • nonloacal:

    • 它的作用是把变量标记为自由变量,即使在函数中为变量赋予新值了,也会变成自由变量。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      def make_averager():
      count = 0
      total = 0
      def averager(new_value):
      nonlocal count, total
      count += 1
      total += new_value
      return total / count
      return averager
  • global:

    • global 标志实际上是为了提示 python 解释器,表明被其修饰的变量是全局变量。这样解释器就可以从当前空间 (current scope) 中读写相应变量了。

    • Python 的全局变量是模块 (module) 级别的

    • 每个 python 函数拥有对应的 __globals__ 字典,该字典与函数所属模块的 __dict__ 字典完全相同。函数的全局变量也会从这个字典中获取

    • 假设我们在 foo.py 所有函数的外部预先定义了全局变量 a ,那么在将函数 f 导入时,a 会随着 f.__globals__ 一同被导入。但这时被导入的 f.__globals__["a"] ( 即 foo.__dict__["a"] ) 和 bar.main 中赋值的 bar.main.__globals__["a"] ( 即 bar.__dict__["a"] ) 仍然不是同一个变量,即赋值无法改变函数 f 的输出,如下面的例子所示。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      # foo.py
      a = 3

      def f():
      print(a)

      def main():
      global a
      a = 5
      f()

      if __name__ == '__main__':
      main()
      1
      2
      3
      4
      5
      6
      7
      8
      9
      # bar.py
      from foo import f

      def main():
      global a
      a = 4
      f()

      main()

      运行 bar.py 输出3,而不是 4。就上述例子而言,如果我们想在 bar.py 中改变函数 f 的输出,则需要直接更新其 __globals__ 变量的值。

      1
      2
      3
      4
      5
      6
      7
      8
      # bar.py
      from foo import f

      def main():
      f.__globals__['a'] = 4
      f()

      main()

20. 类定义

当(从结尾处)正常离开类定义时,将创建一个 类对象。 这基本上是一个包围在类定义所创建命名空间内容周围的包装器

类对象支持两种操作:属性引用和实例化。

  • 属性引用 使用 Python 中所有属性引用所使用的标准语法: obj.name。

  • 实例化

    1
    x = MyClass()

    当一个类定义了 __init__() 方法时,类的实例化操作会自动为新创建的类实例发起调用 __init__()。


1
2
3
4
5
6
class MyClass:
"""A simple example class"""
i = 12345

def f(self):
return 'hello world'

20.1. 实例对象

实例对象理解的唯一操作是属性引用,有两种有效的属性名称,数据属性和方法。

  • 数据属性:对应于C++ 中的“数据成员

    数据属性不需要声明;像局部变量一样,它们将在第一次被赋值时产生。

但是 x.f 与 MyClass.f 并不是一回事 — 它是一个 方法对象,不是函数对象

20.2. 方法对象

通常,方法在绑定后立即被调用:

1
x.f()

在 MyClass 示例中,这将返回字符串 'hello world'。 但是,立即调用一个方法并不是必须的: x.f 是一个方法对象,它可以被保存起来以后再调用。 例如:

1
2
3
xf = x.f
while True:
print(xf())

实际上,你可能已经猜到了答案:方法的特殊之处就在于实例对象会作为函数的第一个参数被传入。 在我们的示例中,调用 x.f() 其实就相当于 MyClass.f(x)。

当一个实例的非数据属性被引用时,将搜索实例所属的类。 如果名称表示一个属于函数对象的有效类属性,会通过合并打包(指向)实例对象和函数对象到一个抽象对象中的方式来创建一个方法对象:这个抽象对象就是方法对象。 当附带参数列表调用方法对象时,将基于实例对象和参数列表构建一个新的参数列表,并使用这个新参数列表调用相应的函数对象。

21. 类和实例变量

一般来说,实例变量用于每个实例的唯一数据,而类变量用于类的所有实例共享的属性和方法

共享数据可能在涉及 mutable 对象例如列表和字典的时候导致令人惊讶的结果。例如以下代码中的 tricks 列表不应该被用作类变量,因为所有的 Dog 实例将只共享一个单独的列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Dog:

tricks = [] # mistaken use of a class variable

def __init__(self, name):
self.name = name

def add_trick(self, trick):
self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks # unexpectedly shared by all dogs
['roll over', 'play dead']

正确的类设计应该使用实例变量:

1
2
3
4
5
6
7
8
class Dog:

def __init__(self, name):
self.name = name
self.tricks = [] # creates a new empty list for each dog

def add_trick(self, trick):
self.tricks.append(trick)

22. 补充说明

  1. 数据属性覆盖具有相同名称的方法属性

  2. 客户端应当谨慎地使用数据属性 — 客户端可能通过直接操作数据属性的方式破坏由方法所维护的固定变量。 请注意客户端可以向一个实例对象添加他们自己的数据属性而不会影响方法的可用性,只要保证避免名称冲突 — 再次提醒,在此使用命名约定可以省去许多令人头痛的麻烦

  3. 方法的第一个参数常常被命名为 self。 这也不过就是一个约定: self 这一名称在 Python 中绝对没有特殊含义。 但是要注意,不遵循此约定会使得你的代码对其他 Python 程序员来说缺乏可读性

  4. 函数定义的文本并非必须包含于类定义之内:将一个函数对象赋值给一个局部变量也是可以的。 例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # Function defined outside the class
    def f1(self, x, y):
    return min(x, x+y)

    class C:
    f = f1

    def g(self):
    return 'hello world'

    h = g

    现在 f, g 和 h 都是 C 类的引用函数对象的属性

23. 继承

1
2
3
4
5
6
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>

名称 BaseClassName 必须定义于包含派生类定义的作用域中。

1
class DerivedClassName(modname.BaseClassName):

派生类可能会重载其基类的方法。 因为方法在调用同一对象的其他方法时没有特殊权限,调用同一基类中定义的另一方法的基类方法最终可能会调用覆盖它的派生类的方法。 (对 C++ 程序员的提示:Python 中所有的方法实际上都是 virtual 方法。)

Python有两个内置函数可被用于继承机制:

  • 使用isinstance()来检查一个实例的类型: isinstance(obj, int) 仅会在 obj.__class__ 为int或某个派生自int的类时为 True。
  • 使用issubclass()来检查类的继承关系: issubclass(bool, int) 为 True,因为bool是int的子类。 但是,issubclass(float, int) 为 False,因为float不是int的子类。

23.1. 多重继承

如果某一属性在 DerivedClassName 中未找到,则会到 Base1 中搜索它,然后(递归地)到 Base1 的基类中搜索,如果在那里未找到,再到 Base2 中搜索,依此类推。

真实情况比这个更复杂一些;方法解析顺序会动态改变以支持对super()的协同调用。 这种方式在某些其他多重继承型语言中被称为后续方法调用,它比单继承型语言中的 super 调用更强大。

动态改变顺序是有必要的,因为所有多重继承的情况都会显示出一个或更多的菱形关联(即至少有一个父类可通过多条路径被最底层类所访问)。 例如,所有类都是继承自object,因此任何多重继承的情况都提供了一条以上的路径可以通向object。 为了确保基类不会被访问一次以上,动态算法会用一种特殊方式将搜索顺序线性化, 保留每个类所指定的从左至右的顺序,只调用每个父类一次,并且保持单调(即一个类可以被子类化而不影响其父类的优先顺序)

24. 私有变量

那种仅限从一个对象内部访问的“私有”实例变量在 Python 中并不存在。 但是,大多数 Python 代码都遵循这样一个约定:带有一个下划线的名称 (例如 _spam) 应该被当作是 API 的非公共部分 (无论它是函数、方法或是数据成员)。 这应当被视为一个实现细节,可能不经通知即加以改变。

由于存在对于类私有成员的有效使用场景(例如避免名称与子类所定义的名称相冲突),因此存在对此种机制的有限支持,称为 名称改写。 任何形式为 __spam 的标识符(至少带有两个前缀下划线,至多一个后缀下划线)的文本将被替换为 _classname__spam,其中 classname 为去除了前缀下划线的当前类名称。 这种改写不考虑标识符的句法位置,只要它出现在类定义内部就会进行。

名称改写有助于让子类重载方法而不破坏类内方法调用。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)

def update(self, iterable):
for item in iterable:
self.items_list.append(item)

__update = update # private copy of original update() method

class MappingSubclass(Mapping):

def update(self, keys, values):
# provides new signature for update()
# but does not break __init__()
for item in zip(keys, values):
self.items_list.append(item)

上面的示例即使在 MappingSubclass 引入了一个 __update 标识符的情况下也不会出错,因为它会在 Mapping 类中被替换为 _Mapping__update 而在 MappingSubclass 类中被替换为 _MappingSubclass__update。

25. struct实现

有时会需要使用类似于 C 的“struct”这样的数据类型,将一些命名数据项捆绑在一起。 这种情况适合定义一个空类:

1
2
3
4
5
6
7
8
9
class Employee:
pass

john = Employee() # Create an empty employee record

# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000

我猜是规范罢了,毕竟共享变量…

26. 迭代器

for语句会调用容器对象中的iter()。 该函数返回一个定义了 __next()__方法的迭代器对象,该方法将逐一访问容器中的元素。 当元素用尽时,__next()__将引发 StopIiteration异常来通知终止 for 循环。 你可以使用 next()内置函数来调用 __next()__方法

  • 凡是可作用于 for 循环的对象都是 Iterable 类型;

  • 凡是可作用于 next() 函数的对象都是 Iterator 类型

  • 集合数据类型如 list 、 dict 、 str 等是 Iterable 但不是 Iterator ,不过可以通过 iter() 函数获得一个 Iterator 对象

  • Python 支持在容器中进行迭代的概念。 这是通过使用两个单独方法来实现的;它们被用于允许用户自定义类对迭代的支持。

    • iterator.__iter()__

      返回迭代器对象本身。 这是同时允许容器和迭代器配合 for和 in 语句使用所必须的。 此方法对应于 Python/C API 中 Python 对象类型结构体的 tp_iter槽位。

    • iterator.__next()__

      从容器中返回下一项。 如果已经没有项可返回,则会引发StopIteration异常。 此方法对应于 Python/C API 中 Python 对象类型结构体的tp_iternext槽位

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    class Fib:
    def __iter__(self):
    print('__iter__ called')
    self.a = 0
    self.b = 1
    self.max = 3
    return self
    def __next__(self):
    print('__next__ called')
    fib = self.a
    if fib > self.max:
    raise StopIteration
    self.a, self.b = self.b, self.a + self.b
    return fib

    for i in Fib(3):
    print(i)
    # 输出
    __iter__ called
    __next__ called
    0
    __next__ called
    1
    __next__ called
    1
    __next__ called
    2
    __next__ called

    简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。

    当函数执行结束时,generator 自动抛出 StopIteration 异常,表示迭代完成。在 for 循环里,无需处理 StopIteration 异常,循环会正常结束。

    generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。

    要注意区分 fab 和 fab(5),fab 是一个 generator function,而 fab(5) 是调用 fab 返回的一个 generator,好比类的定义和类的。

    在一个 generator function 中,如果没有 return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def read_file(fpath): 
    BLOCK_SIZE = 1024
    with open(fpath, 'rb') as f:
    while True:
    block = f.read(BLOCK_SIZE)
    if block:
    yield block
    else:
    return

    另一个 yield 的例子来源于文件读取。如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类。

    相当于O(1)咯

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
    self.data = data
    self.index = len(data)

    def __iter__(self):
    return self

    def __next__(self):
    if self.index == 0:
    raise StopIteration
    self.index = self.index - 1
    return self.data[self.index]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    >>> rev = Reverse('spam') #rev应该是iterator
    >>> iter(rev)
    <__main__.Reverse object at 0x00A1DB50>
    >>> for char in rev:
    ... print(char)
    ...
    m
    a
    p
    s

27. 生成器

生成器一大意义是:不需要提前创建整个序列而预留资源

写法类似标准的函数,但当它们要返回数据时会使用 yield 语句。 每次对生成器调用 next() 时,它会从上次离开位置恢复执行(它会记住上次执行语句时的所有数据值)。

1
2
3
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
1
2
3
4
5
6
7
>>> for char in reverse('golf'):
... print(char)
...
f
l
o
g

27.1. 生成器表达式

某些简单的生成器可以写成简洁的表达式代码,所用语法类似列表推导式,将外层为圆括号而非方括号。 这种表达式被设计用于生成器将立即被外层函数所使用的情况。 生成器表达式相比完整的生成器更紧凑但较不灵活,相比等效的列表推导式则更为节省内存。(大概内层还是调用的yield )

  • 例如:<generator object <genexpr> at 0x1006a0eb0>

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> sum(i*i for i in range(10))                 # sum of squares
285

>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec)) # dot product
260

>>> from math import pi, sin
>>> sine_table = {x: sin(x*pi/180) for x in range(0, 91)}

>>> unique_words = set(word for line in page for word in line.split())

>>> valedictorian = max((student.gpa, student.name) for student in graduates)

>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']

28. 标准库

29. 操作系统接口

1
2
3
4
5
6
>>> import os
>>> os.getcwd() # Return the current working directory
'C:\\Python37'
>>> os.chdir('/server/accesslogs') # Change current working directory
>>> os.system('mkdir today') # Run the command mkdir in the system shell
0

一定要使用 import os 而不是 from os import * 。这将避免内建的open()函数被os.open()隐式替换掉,它们的使用方式大不相同。

在python中, os模块提供了对操作系统进行操作的接口。查看os模块使用的方法为dir(),查看该模块的帮助方法为help(),

1
2
3
4
5
>>> import os
>>> dir(os)
<returns a list of all module functions>
>>> help(os)
<returns an extensive manual page created from the module's docstrings>

30. 文件通配符

glob模块提供了一个在目录中使用通配符搜索创建文件列表的函数:

1
2
3
>>> import glob
>>> glob.glob('*.py')
['primes.py', 'random.py', 'quote.py']

31. 命令行参数

通用实用程序脚本通常需要处理命令行参数。这些参数作为列表存储在 sys 模块的 argv 属性中。例如,以下输出来自在命令行运行 python demo.py one two three

1
2
3
>>> import sys
>>> print(sys.argv)
['demo.py', 'one', 'two', 'three']

32. 错误输出重定向和程序终止

sys模块还具有 stdin , stdout 和 stderr 的属性。后者对于发出警告和错误消息非常有用,即使在 stdout 被重定向后也可以看到它们:

1
2
>>> sys.stderr.write('Warning, log file not found starting a new one\n')
Warning, log file not found starting a new one

终止脚本的最直接方法是使用 sys.exit()

33. 字符串模式匹配

  • re模块为高级字符串处理提供正则表达式工具。对于复杂的匹配和操作,正则表达式提供简洁,优化的解决方案。

  • 当只需要简单的功能时,首选字符串方法因为它们更容易阅读和调试:

    1
    2
    >>> 'tea for too'.replace('too', 'two')
    'tea for two'

34. 数学

math:

1
2
3
4
>>> math.cos(math.pi / 4)
0.70710678118654757
>>> math.log(1024, 2)
10.0

random:

1
2
3
4
5
6
7
8
>>> random.choice(['apple', 'pear', 'banana'])
'apple'
>>> random.sample(range(100), 10) # sampling without replacement
[30, 83, 16, 4, 8, 81, 41, 50, 18, 33]
>>> random.random() # random float
0.17970987693706186
>>> random.randrange(6) # random integer chosen from range(6)
4

statistics:

1
2
3
4
5
6
7
>>> data = [2.75, 1.75, 1.25, 0.25, 0.5, 1.25, 3.5]
>>> statistics.mean(data)
1.6071428571428572
>>> statistics.median(data)
1.25
>>> statistics.variance(data)
1.3720238095238095

35. 互联网访问

  • urllib
  • smtplib

36. 时期和时间

datetime:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> # dates are easily constructed and formatted
>>> from datetime import date
>>> now = date.today()
>>> now
datetime.date(2003, 12, 2)
>>> now.strftime("%m-%d-%y. %d %b %Y is a %A on the %d day of %B.")
'12-02-03. 02 Dec 2003 is a Tuesday on the 02 day of December.'

>>> # dates support calendar arithmetic
>>> birthday = date(1964, 7, 31)
>>> age = now - birthday
>>> age.days
14368

37. 数据压缩

  • zlib
  • gzip
1
2
3
4
5
6
7
8
9
10
11
>>> import zlib
>>> s = b'witch which has which witches wrist watch'
>>> len(s)
41
>>> t = zlib.compress(s)
>>> len(t)
37
>>> zlib.decompress(t)
b'witch which has which witches wrist watch'
>>> zlib.crc32(s)
226805979

38. 性能测试

  • timeit:

    1
    2
    3
    4
    5
    >>> from timeit import Timer
    >>> Timer('t=a; a=b; b=t', 'a=1; b=2').timeit()
    0.57535828626024577
    >>> Timer('a,b = b,a', 'a=1; b=2').timeit()
    0.54962537085770791

    元组封包和拆包功能相比传统的交换参数可能更具吸引力。

  • profile

  • pstats

39. 质量控制

模块提供了一个工具,用于扫描模块并验证程序文档字符串中嵌入的测试。测试构造就像将典型调用及其结果剪切并粘贴到文档字符串一样简单。这通过向用户提供示例来改进文档,并且它允许doctest模块确保代码保持对文档的真实:

1
2
3
4
5
6
7
8
9
10
def average(values):
"""Computes the arithmetic mean of a list of numbers.

>>> print(average([20, 30, 70]))
40.0
"""
return sum(values) / len(values)

import doctest
doctest.testmod() # automatically validate the embedded tests

40. 格式化输出

  • reprlib模块提供了一个定制化版本的repr()函数,用于缩略显示大型或深层嵌套的容器对象:

    1
    2
    3
    >>> import reprlib
    >>> reprlib.repr(set('supercalifragilisticexpialidocious'))
    "{'a', 'c', 'd', 'e', 'f', 'g', ...}"
  • textwrap 模块能够格式化文本段落,以适应给定的屏幕宽度

  • locale模块处理与特定地域文化相关的数据格式。locale 模块的 format 函数包含一个 grouping 属性,可直接将数字格式化为带有组分隔符的样式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    >>> import locale
    >>> locale.setlocale(locale.LC_ALL, 'English_United States.1252')
    'English_United States.1252'
    >>> conv = locale.localeconv() # get a mapping of conventions
    >>> x = 1234567.8
    >>> locale.format("%d", x, grouping=True)
    '1,234,567'
    >>> locale.format_string("%s%.*f", (conv['currency_symbol'],
    ... conv['frac_digits'], x), grouping=True)
    '$1,234,567.80'
  • pprint 模块提供了更加复杂的打印控制,其输出的内置对象和用户自定义对象能够被解释器直接读取。当输出结果过长而需要折行时,“美化输出机制”会添加换行符和缩进,以更清楚地展示数据结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    >>> import pprint
    >>> t = [[[['black', 'cyan'], 'white', ['green', 'red']], [['magenta',
    ... 'yellow'], 'blue']]]
    ...
    >>> pprint.pprint(t, width=30)
    [[[['black', 'cyan'],
    'white',
    ['green', 'red']],
    [['magenta', 'yellow'],
    'blue']]]

41. 模板

string模块包含一个通用的Template类,具有适用于最终用户的简化语法。它允许用户在不更改应用逻辑的情况下定制自己的应用。

上述格式化操作是通过占位符实现的,占位符由 $ 加上合法的 Python 标识符(只能包含字母、数字和下划线)构成。一旦使用花括号将占位符括起来,就可以在后面直接跟上更多的字母和数字而无需空格分割。$$ 将被转义成单个字符 $:

1
2
3
4
>>> from string import Template
>>> t = Template('${village}folk send $$10 to $cause.')
>>> t.substitute(village='Nottingham', cause='the ditch fund')
'Nottinghamfolk send $10 to the ditch fund.'

如果在字典或关键字参数中未提供某个占位符的值,那么 substitute() 方法将抛出 KeyError。对于邮件合并类型的应用,用户提供的数据有可能是不完整的,此时使用 safe_substitute() 方法更加合适 —— 如果数据缺失,它会直接将占位符原样保留。

1
2
3
4
5
6
7
8
>>> t = Template('Return the $item to $owner.')
>>> d = dict(item='unladen swallow')
>>> t.substitute(d)
Traceback (most recent call last):
...
KeyError: 'owner'
>>> t.safe_substitute(d)
'Return the unladen swallow to $owner.'

Template 的子类可以自定义定界符。例如,以下是某个照片浏览器的批量重命名功能,采用了百分号作为日期、照片序号和照片格式的占位符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> import time, os.path
>>> photofiles = ['img_1074.jpg', 'img_1076.jpg', 'img_1077.jpg']
>>> class BatchRename(Template):
... delimiter = '%'
>>> fmt = input('Enter rename style (%d-date %n-seqnum %f-format): ')
Enter rename style (%d-date %n-seqnum %f-format): Ashley_%n%f

>>> t = BatchRename(fmt)
>>> date = time.strftime('%d%b%y')
>>> for i, filename in enumerate(photofiles):
... base, ext = os.path.splitext(filename)
... newname = t.substitute(d=date, n=i, f=ext)
... print('{0} --> {1}'.format(filename, newname))

img_1074.jpg --> Ashley_0.jpg
img_1076.jpg --> Ashley_1.jpg
img_1077.jpg --> Ashley_2.jpg

42. 使用二进制数据记录格式

  • struct
    • pack()
    • unpack()

43. 多线程

线程是一种对于非顺序依赖的多个任务进行解耦的技术。多线程可以提高应用的响应效率,当接收用户输入的同时,保持其他任务在后台运行。一个有关的应用场景是,将 I/O 和计算运行在两个并行的线程中。

多线程应用面临的主要挑战是,相互协调的多个线程之间需要共享数据或其他资源。为此,threading 模块提供了多个同步操作原语,包括线程锁、事件、条件变量和信号量。

尽管这些工具非常强大,但微小的设计错误却可以导致一些难以复现的问题。因此,实现多任务协作的首选方法是将对资源的所有请求集中到一个线程中,然后使用 queue 模块向该线程供应来自其他线程的请求。应用程序使用 Queue 对象进行线程间通信和协调,更易于设计,更易读,更可靠。

44. 日志

logging模块提供功能齐全且灵活的日志记录系统。在最简单的情况下,日志消息被发送到文件或 sys.stderr

1
2
3
4
5
6
import logging
logging.debug('Debugging information')
logging.info('Informational message')
logging.warning('Warning:config file %s not found', 'server.conf')
logging.error('Error occurred')
logging.critical('Critical error -- shutting down')

45. 弱引用

Python 会自动进行内存管理。 当某个对象的最后一个引用被移除后不久就会释放其所占用的内存。

此方式对大多数应用来说都适用,但偶尔也必须在对象持续被其他对象所使用时跟踪它们。 不幸的是,跟踪它们将创建一个会令其永久化的引用。weakref模块提供的工具可以不必创建引用就能跟踪对象。 当对象不再需要时,它将自动从一个弱引用表中被移除,并为弱引用对象触发一个回调。 典型应用包括对创建开销较大的对象进行缓存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> import weakref, gc
>>> class A:
... def __init__(self, value):
... self.value = value
... def __repr__(self):
... return str(self.value)
...
>>> a = A(10) # create a reference
>>> d = weakref.WeakValueDictionary()
>>> d['primary'] = a # does not create a reference
>>> d['primary'] # fetch the object if it is still alive
10
>>> del a # remove the one reference
>>> gc.collect() # run garbage collection right away
0
>>> d['primary'] # entry was automatically removed
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
d['primary'] # entry was automatically removed
File "C:/python37/lib/weakref.py", line 46, in __getitem__
o = self.data[key]()
KeyError: 'primary'

46. 类列表

  1. Array模块提供了一种array()对象,它类似于列表,但只能存储类型一致的数据且存储密集更高。 下面的例子演示了一个以两个字节为存储单元的无符号二进制数值的数组 (类型码为 "H"),而对于普通列表来说,每个条目存储为标准 Python 的 int 对象通常要占用16 个字节:

    1
    2
    3
    4
    5
    6
    >>> from array import array
    >>> a = array('H', [4000, 10, 700, 22222])
    >>> sum(a)
    26932
    >>> a[1:3]
    array('H', [10, 700])
  2. collections 模块提供了一种 deque() 对象,它类似于列表,但从左端添加和弹出的速度较快,而在中间查找的速度较慢。 此种对象适用于实现队列和广度优先树搜索

  3. heapq 模块提供了基于常规列表来实现堆的函数。 最小值的条目总是保持在位置零。 这对于需要重复访问最小元素而不希望运行完整列表排序的应用来说非常有用:

47. 十进制浮点数

decimal 模块提供了一种 Decimal 数据类型用于十进制浮点运算。 相比内置的 float 二进制浮点实现,该类特别适用于

  • 财务应用和其他需要精确十进制表示的用途,
  • 控制精度,
  • 控制四舍五入以满足法律或监管要求,
  • 跟踪有效小数位,或
  • 用户期望结果与手工完成的计算相匹配的应用程序。
1
2
3
4
5
>>> from decimal import *
>>> round(Decimal('0.70') * Decimal('1.05'), 2)
Decimal('0.74')
>>> round(.70 * 1.05, 2)
0.73

精确表示特性使得 Decimal 类能够执行对于二进制浮点数来说不适用的模运算和相等性检测

1
2
3
4
5
6
7
8
9
>>> Decimal('1.00') % Decimal('.10')
Decimal('0.00')
>>> 1.00 % 0.10
0.09999999999999995

>>> sum([Decimal('0.1')]*10) == Decimal('1.0')
True
>>> sum([0.1]*10) == 1.0
False
  • Program Language
  • Python
  • Basic
Make&&Valgrind
Python入门-1
  1. 1. 1. 模块
    1. 1.1. 2. 以脚本的方式
      1. 1.1.1. 2.1. 模块路径
      2. 1.1.2. 2.2. 编译后
    2. 1.2. 3. dir()
  2. 2. 4. 包
    1. 2.1. 5. Import *
    2. 2.2. 6. 子包
  3. 3. 7. 输入输出
    1. 3.1. 8. 格式化输出
    2. 3.2. 9. 手动格式化字符串
      1. 3.2.1. 9.1. 旧式
    3. 3.3. 10. 读写文件
  4. 4. 11. json
  5. 5. 12. 错误与异常
    1. 5.1. 13. 语法错误
    2. 5.2. 14. 异常
      1. 5.2.1. 14.1. 处理异常
    3. 5.3. 15. 抛出异常
    4. 5.4. 16. 用户自定义异常
    5. 5.5. 17. 预定义的清理操作
  6. 6. 18. 类
    1. 6.1. 19. 命名空间
    2. 6.2. 20. 类定义
      1. 6.2.1. 20.1. 实例对象
      2. 6.2.2. 20.2. 方法对象
    3. 6.3. 21. 类和实例变量
    4. 6.4. 22. 补充说明
    5. 6.5. 23. 继承
      1. 6.5.1. 23.1. 多重继承
    6. 6.6. 24. 私有变量
    7. 6.7. 25. struct实现
  7. 7. 26. 迭代器
    1. 7.1. 27. 生成器
      1. 7.1.1. 27.1. 生成器表达式
  8. 8. 28. 标准库
    1. 8.1. 29. 操作系统接口
    2. 8.2. 30. 文件通配符
    3. 8.3. 31. 命令行参数
    4. 8.4. 32. 错误输出重定向和程序终止
    5. 8.5. 33. 字符串模式匹配
    6. 8.6. 34. 数学
    7. 8.7. 35. 互联网访问
    8. 8.8. 36. 时期和时间
    9. 8.9. 37. 数据压缩
    10. 8.10. 38. 性能测试
    11. 8.11. 39. 质量控制
    12. 8.12. 40. 格式化输出
    13. 8.13. 41. 模板
    14. 8.14. 42. 使用二进制数据记录格式
    15. 8.15. 43. 多线程
    16. 8.16. 44. 日志
    17. 8.17. 45. 弱引用
    18. 8.18. 46. 类列表
    19. 8.19. 47. 十进制浮点数
© 2024 何决云 载入天数...