# 程式開發常見問答集

目錄

- 程式開發常見問答集

  - 常見問題

    - 是否有可以使用在程式碼階段,具有中斷點,步驟執行等功能的除錯器？

    - 是否有能帮助寻找漏洞或执行静态分析的工具？

    - 如何由 Python 脚本创建能独立运行的二进制程序？

    - 是否有 Python 编码标准或风格指南？

  - 语言核心内容

    - 為什麼當變數有值時我得到錯誤訊息 UnboundLocalError

    - Python 的區域變數和全域變數有什麼規則？

    - 为什么在循环中定义的参数各异的 lambda 都返回相同的结果？

    - 如何跨模块共享全局变量？

    - 导入模块的“最佳实践”是什么？

    - 为什么对象之间会共享默认值？

    - 如何将可选参数或关键字参数从一个函数传递到另一个函数？

    - 形参和实参之间有什么区别？

    - 为什么修改列表 'y' 也会更改列表 'x'？

    - 如何编写带有输出参数的函数（按照引用调用）？

    - 如何在 Python 中创建高阶函数？

    - 如何复制 Python 对象？

    - 如何找到对象的方法或属性？

    - 如何用代码获取对象的名称？

    - 逗号运算符的优先级是什么？

    - 是否提供等价于 C 语言 "?:" 三目运算符的东西？

    - 是否可以用 Python 编写让人眼晕的单行程序?

    - 函数形参列表中的斜杠（/）是什么意思？

  - 数字和字符串

    - 如何给出十六进制和八进制整数？

    - 为什么 -22 // 10 会返回 -3 ？

    - 如何将字符串转换为数字？

    - 如何将数字转换为字符串？

    - 如何修改字符串？

    - 如何使用字符串调用函数/方法？

    - 是否有与Perl 的chomp() 等效的方法，用于从字符串中删除尾随换行符？

    - 是否有 scanf() 或 sscanf() 的等价函数？

    - 'UnicodeDecodeError' 或 'UnicodeEncodeError' 错误是什么意思？

  - 性能

    - 我的程序太慢了。该如何加快速度？

    - 将多个字符串连接在一起的最有效方法是什么？

  - 序列（元组/列表）

    - 如何在元组和列表之间进行转换？

    - 什么是负数索引？

    - 序列如何以逆序遍历？

    - 如何从列表中删除重复项？

    - 如何从列表中删除多个项？

    - 如何在 Python 中创建数组？

    - 如何创建多维列表？

    - 如何将方法应用于一系列对象？

    - 为什么 a_tuple[i] += ['item'] 会引发异常？

    - 我想做一个复杂的排序：能用 Python 进行施瓦茨变换吗？

    - 如何根据另一个列表的值对某列表进行排序？

  - 对象

    - 什么是类？

    - 什么是方法？

    - 什么是 self ？

    - 如何检查对象是否为给定类或其子类的一个实例？

    - 什么是委托？

    - 如何在派生类中调用被重载的基类方法?

    - 如何让代码更容易对基类进行修改？

    - 如何创建静态类数据和静态类方法？

    - 在 Python 中如何重载构造函数（或方法）？

    - 在用 __spam 的时候得到一个类似 _SomeClassName__spam 的错误信息。

    - 类定义了 __del__ 方法，但是删除对象时没有调用它。

    - 如何获取给定类的所有实例的列表？

    - 为什么 `id()` 的结果看起来不是唯一的？

    - 什么情况下可以依靠 is 运算符进行对象的身份相等性测试？

    - How can a subclass control what data is stored in an immutable instance?

  - 模块

    - 如何创建 .pyc 文件？

    - 如何找到当前模块名称？

    - 如何让模块相互导入？

    - __import__('x.y.z') 返回的是 <module 'x'> ；该如何得到 z 呢？

    - 对已导入的模块进行了编辑并重新导入，但变动没有得以体现。这是为什么？

## 常見問題

### 是否有可以使用在程式碼階段,具有中斷點,步驟執行等功能的除錯器？

有的

以下介绍了一些 Python 的调试器，用内置函数 `breakpoint()` 即可切入这些调试器中。

pdb 模块是一个简单但是够用的控制台模式 Python 调试器。 它是标准 Python 库的一部分，并且 `已收录于库参考手册`。 你也可以通过使用 pdb 代码作为样例来编写你自己的调试器。

作为标准 Python 发行版附带组件的 IDLE 交互式环境（通常位于 Tools/scripts/idle）中包含一个图形化的调试器。

PythonWin 是一种 Python IDE，其中包含了一个基于 pdb 的 GUI 调试器。PythonWin 的调试器会为断点着色，并提供了相当多的超酷特性，例如调试非 PythonWin 程序等。PythonWin 是 pywin32 项目的组成部分，也是 ActivePython 发行版的组成部分。

Eric 是一个基于PyQt和Scintilla编辑组件构建的IDE。

trepan3k 是一个类似 gdb 的调试器。

Visual Studio Code 是包含了调试工具的 IDE，并集成了版本控制软件。

有數個商業化Python整合化開發工具包含圖形除錯功能。這些包含：

- Wing IDE

- Komodo IDE

- PyCharm

### 是否有能帮助寻找漏洞或执行静态分析的工具？

有的

Pylint 和 Pyflakes 可进行基本检查来帮助你尽早捕捉漏洞。

静态类型检查器，例如 Mypy 、 Pyre 和 Pytype 可以检查Python源代码中的类型提示。

### 如何由 Python 脚本创建能独立运行的二进制程序？

如果只是想要一个独立的程序，以便用户不必预先安装 Python 即可下载和运行它，则不需要将 Python 编译成 C 代码。有许多工具可以检测程序所需的模块，并将这些模块与 Python 二进制程序捆绑在一起生成单个可执行文件。

一种方案是使用 freeze 工具，它在 Python 源码目录的 `Tools/freeze` 中提供。它能把 Python 字节码转换为 C 数组；一个 C 编译器可以将所有模块嵌入到一个新程序中，然后将其与标准 Python 模块链接。

它的工作原理是递归扫描源代码，获取两种格式的 import 语句，并在标准 Python 路径和源码目录（用于内置模块）检索这些模块。然后，把这些模块的 Python 字节码转换为 C 代码（可以利用 marshal 模块转换为代码对象的数组初始化器），并创建一个定制的配置文件，该文件仅包含程序实际用到的内置模块。然后，编译生成的 C 代码并将其与 Python 解释器的其余部分链接，形成一个自给自足的二进制文件，其功能与 Python 脚本代码完全相同。

下列包可以用于帮助创建控制台和 GUI 的可执行文件:

- Nuitka （跨平台）

- PyInstaller （跨平台）

- PyOxidizer （跨平台）

- cx_Freeze （跨平台）

- py2app （仅限 macOS）

- py2exe （仅限 Windows）

### 是否有 Python 编码标准或风格指南？

有的。 标准库模块所要求的编码风格记录于 PEP 8 之中。

## 语言核心内容

### 為什麼當變數有值時我得到錯誤訊息 UnboundLocalError

因为在函数内部某处添加了一条赋值语句，导致之前正常工作的代码报出 UnboundLocalError 错误，这可能是有点令人惊讶。

這段程式碼:

    >>> x = 10
    >>> def bar():
    ...     print(x)
    >>> bar()
    10

可以執行,但是這段程式:

    >>> x = 10
    >>> def foo():
    ...     print(x)
    ...     x += 1

導致UnboundLocalError

    >>> foo()
    Traceback (most recent call last):
      ...
    UnboundLocalError: local variable 'x' referenced before assignment

原因就是，当对某作用域内的变量进行赋值时，该变量将成为该作用域内的局部变量，并覆盖外部作用域中的同名变量。由于 foo 的最后一条语句为 `x` 分配了一个新值，编译器会将其识别为局部变量。因此，前面的 `print(x)` 试图输出未初始化的局部变量，就会引发错误。

在上面的示例中，可以将外部作用域的变量声明为全局变量以便访问：

    >>> x = 10
    >>> def foobar():
    ...     global x
    ...     print(x)
    ...     x += 1
    >>> foobar()
    10

与类和实例变量貌似但不一样，其实以上是在修改外部作用域的变量值，为了提示这一点，这里需要显式声明一下。

    >>> print(x)
    11

你可以使用 `nonlocal` 关键字在嵌套作用域中执行类似的操作：

    >>> def foo():
    ...    x = 10
    ...    def bar():
    ...        nonlocal x
    ...        print(x)
    ...        x += 1
    ...    bar()
    ...    print(x)
    >>> foo()
    10
    11

### Python 的區域變數和全域變數有什麼規則？

函数内部只作引用的 Python 变量隐式视为全局变量。如果在函数内部任何位置为变量赋值，则除非明确声明为全局变量，否则均将其视为局部变量。

起初尽管有点令人惊讶，不过考虑片刻即可释然。一方面，已分配的变量要求加上 `global` 可以防止意外的副作用发生。另一方面，如果所有全局引用都要加上 `global` ，那处处都得用上 `global` 了。那么每次对内置函数或导入模块中的组件进行引用时，都得声明为全局变量。这种杂乱会破坏 `global` 声明用于警示副作用的有效性。

### 为什么在循环中定义的参数各异的 lambda 都返回相同的结果？

假设用 for 循环来定义几个取值各异的 lambda（即便是普通函数也一样）：

    >>> squares = []
    >>> for x in range(5):
    ...     squares.append(lambda: x**2)

以上会得到一个包含5个 lambda 函数的列表，这些函数将计算 `x**2`。大家或许期望，调用这些函数会分别返回 `0` 、`1` 、 `4` 、 `9` 和 `16`。然而，真的试过就会发现，他们都会返回 `16` ：

    >>> squares[2]()
    16
    >>> squares[4]()
    16

这是因为 `x` 不是 lambda 函数的内部变量，而是定义于外部作用域中的，并且 `x` 是在调用 lambda 时访问的——而不是在定义时访问。循环结束时 `x` 的值是 `4` ，所以此时所有的函数都将返回 `4**2` ，即 `16` 。通过改变 `x` 的值并查看 lambda 的结果变化，也可以验证这一点。

    >>> x = 8
    >>> squares[2]()
    64

为了避免发生上述情况，需要将值保存在 lambda 局部变量，以使其不依赖于全局 `x` 的值：

    >>> squares = []
    >>> for x in range(5):
    ...     squares.append(lambda n=x: n**2)

以上 `n=x` 创建了一个新的 lambda 本地变量 `n`，并在定义 lambda 时计算其值，使其与循环当前时点的 `x` 值相同。这意味着 `n` 的值在第 1 个 lambda 中为 `0` ，在第 2 个 lambda 中为 `1` ，在第 3 个中为 `2`，依此类推。因此现在每个 lambda 都会返回正确结果：

    >>> squares[2]()
    4
    >>> squares[4]()
    16

请注意，上述表现并不是 lambda 所特有的，常规的函数也同样适用。

### 如何跨模块共享全局变量？

在单个程序中跨模块共享信息的规范方法是创建一个特殊模块（通常称为 config 或 cfg）。只需在应用程序的所有模块中导入该 config 模块；然后该模块就可当作全局名称使用了。因为每个模块只有一个实例，所以对该模块对象所做的任何更改将会在所有地方得以体现。 例如：

config.py：

    x = 0   # Default value of the 'x' configuration setting

mod.py：

    import config
    config.x = 1

main.py：

    import config
    import mod
    print(config.x)

请注意，出于同样的原因，采用模块也是实现单例设计模式的基础。

### 导入模块的“最佳实践”是什么？

通常请勿使用 `from modulename import *` 。因为这会扰乱 importer 的命名空间，且会造成未定义名称更难以被 Linter 检查出来。

请在代码文件的首部就导入模块。这样代码所需的模块就一目了然了，也不用考虑模块名是否在作用域内的问题。每行导入一个模块则增删起来会比较容易，每行导入多个模块则更节省屏幕空间。

按如下顺序导入模块就是一种好做法：

1. 标准库模块——比如： `sys` 、 `os` 、 `getopt` 、 `re` 等。

2. 第三方库模块（安装于 Python site-packages 目录中的内容）——如 mx.DateTime、ZODB、PIL.Image 等。

3. 本地开发的模块

为了避免循环导入引发的问题，有时需要将模块导入语句移入函数或类的内部。Gordon McMillan 的说法如下：

> 当两个模块都采用 "import <module>" 的导入形式时，循环导入是没有问题的。但如果第 2 个模块想从第 1 个模块中取出一个名称（"from module import name"）并且导入处于代码的最顶层，那导入就会失败。原因是第 1 个模块中的名称还不可用，这时第 1 个模块正忙于导入第 2 个模块呢。

如果只是在一个函数中用到第 2 个模块，那这时将导入语句移入该函数内部即可。当调用到导入语句时，第 1 个模块将已经完成初始化，第 2 个模块就可以进行导入了。

如果某些模块是平台相关的，可能还需要把导入语句移出最顶级代码。这种情况下，甚至有可能无法导入文件首部的所有模块。于是在对应的平台相关代码中导入正确的模块，就是一种不错的选择。

只有为了避免循环导入问题，或有必要减少模块初始化时间时，才把导入语句移入类似函数定义内部的局部作用域。如果根据程序的执行方式，许多导入操作不是必需的，那么这种技术尤其有用。如果模块仅在某个函数中用到，可能还要将导入操作移入该函数内部。请注意，因为模块有一次初始化过程，所以第一次加载模块的代价可能会比较高，但多次加载几乎没有什么花费，代价只是进行几次字典检索而已。即使模块名超出了作用域，模块在 `sys.modules` 中也是可用的。

### 为什么对象之间会共享默认值？

新手程序员常常中招这类 Bug。请看以下函数：

    def foo(mydict={}):  # Danger: shared reference to one dict for all calls
        ... compute something ...
        mydict[key] = value
        return mydict

第一次调用此函数时， `mydict` 中只有一个数据项。第二次调用 `mydict` 则会包含两个数据项，因为 `foo()` 开始执行时， `mydict` 中已经带有一个数据项了。

大家往往希望，函数调用会为默认值创建新的对象。但事实并非如此。默认值只会在函数定义时创建一次。如果对象发生改变，就如上例中的字典那样，则后续调用该函数时将会引用这个改动的对象。

按照定义，不可变对象改动起来是安全的，诸如数字、字符串、元组和 `None` 之类。而可变对象的改动则可能引起困惑，例如字典、列表和类实例等。

因此，不把可变对象用作默认值是一种良好的编程做法。而应采用 `None` 作为默认值，然后在函数中检查参数是否为 `None` 并新建列表、字典或其他对象。例如，代码不应如下所示：

    def foo(mydict={}):
        ...

而应这么写：

    def foo(mydict=None):
        if mydict is None:
            mydict = {}  # create a new dict for local namespace

参数默认值的特性有时会很有用处。 如果有个函数的计算过程会比较耗时，有一种常见技巧是将每次函数调用的参数和结果缓存起来，并在同样的值被再次请求时返回缓存的值。这种技巧被称为“memoize”，实现代码可如下所示：

    # Callers can only provide two parameters and optionally pass _cache by keyword
    def expensive(arg1, arg2, *, _cache={}):
        if (arg1, arg2) in _cache:
            return _cache[(arg1, arg2)]

        # Calculate the value
        result = ... expensive computation ...
        _cache[(arg1, arg2)] = result           # Store result in the cache
        return result

也可以不用参数默认值来实现，而是采用全局的字典变量；这取决于个人偏好。

### 如何将可选参数或关键字参数从一个函数传递到另一个函数？

请利用函数参数列表中的标识符 `*` 和 `**` 归集实参；结果会是元组形式的位置实参和字典形式的关键字实参。然后就可利用 `*` 和 `**` 在调用其他函数时传入这些实参：

    def f(x, *args, **kwargs):
        ...
        kwargs['width'] = '14.3c'
        ...
        g(x, *args, **kwargs)

### 形参和实参之间有什么区别？

形参 是指出现在函数定义中的名称，而 实参 则是在调用函数时实际传入的值。 形参定义了一个函数能接受何种类型的实参。 例如，对于以下函数定义:

    def func(foo, bar=None, **kwargs):
        pass

foo 、 bar 和 kwargs 是 `func` 的形参。 不过在调用 `func` 时，例如：

    func(42, bar=314, extra=somevar)

`42` 、 `314` 和 `somevar` 则是实参。

### 为什么修改列表 'y' 也会更改列表 'x'？

如果代码编写如下：

    >>> x = []
    >>> y = x
    >>> y.append(10)
    >>> y
    [10]
    >>> x
    [10]

或许大家很想知道，为什么在 y 中添加一个元素时， x 也会改变。

产生这种结果有两个因素：

1. 变量只是指向对象的一个名称。执行 `y = x` 并不会创建列表的副本——而只是创建了一个新变量 `y`，并指向 `x` 所指的同一对象。这就意味着只存在一个列表对象，`x` 和 `y` 都是对它的引用。

2. 列表属于 mutable 对象，这意味着它的内容是可以修改的。

在调用 `append()` 之后，该可变对象的内容由 `[]` 变为 `[10]`。由于 x 和 y 这两个变量引用了同一对象，因此用其中任意一个名称所访问到的都是修改后的值 `[10]`。

如果把赋给 `x` 的对象换成一个不可变对象：

    >>> x = 5  # ints are immutable
    >>> y = x
    >>> x = x + 1  # 5 can't be mutated, we are creating a new object here
    >>> x
    6
    >>> y
    5

可见这时 `x` 和 `y` 就不再相等了。因为整数是 immutable 对象，在执行 `x = x + 1` 时，并不会修改整数对象 `5`，给它加上 1；而是创建了一个新的对象（整数对象 `6` ）并将其赋给 `x` （也就是改变了 `x` 所指向的对象）。在赋值完成后，就有了两个对象（整数对象 `6` 和 `5` ）和分别指向他俩的两个变量（ `x` 现在指向 `6` 而 `y` 仍然指向 `5` ）。

某些操作（例如 `y.append(10)` 和 `y.sort()` ）会直接修改原对象，而看上去相似的另一些操作（例如 `y = y + [10]` 和 `sorted(y)` ）则会创建新的对象。通常在 Python 中（以及所有标准库），直接修改原对象的方法将会返回 `None` ，以助避免混淆这两种不同类型的操作。因此如果误用了 `y.sort()` 并期望返回 `y` 的有序副本，则结果只会是 `None` ，这可能就能让程序引发一条容易诊断的错误。

不过还存在一类操作，用不同的类型执行相同的操作有时会发生不同的行为：即增量赋值运算符。例如，`+=` 会修改列表，但不会修改元组或整数（`a_list += [1, 2, 3]` 与 `a_list.extend([1, 2, 3])` 同样都会改变 `a_list`，而 `some_tuple += (1, 2, 3)` 和 `some_int += 1` 则会创建新的对象）。

换而言之：

- 对于一个可变对象（ `list` 、 `dict` 、 `set` 等等），可以利用某些特定的操作进行修改，所有引用它的变量都会反映出改动情况。

- 对于一个不可变对象（ `str` 、 `int` 、 `tuple` 等），所有引用它的变量都会给出相同的值，但所有改变其值的操作都将返回一个新的对象。

如要知道两个变量是否指向同一个对象，可以利用 `is` 运算符或内置函数 `id()`。

### 如何编写带有输出参数的函数（按照引用调用）？

请记住，Python 中的实参是通过赋值传递的。由于赋值只是创建了对象的引用，所以调用方和被调用方的参数名都不存在别名，本质上也就不存在按引用调用的方式。通过以下几种方式，可以得到所需的效果。

1. 返回一个元组：

       >>> def func1(a, b):
       ...     a = 'new-value'        # a and b are local names
       ...     b = b + 1              # assigned to new objects
       ...     return a, b            # return new values
       ...
       >>> x, y = 'old-value', 99
       >>> func1(x, y)
       ('new-value', 100)

   这差不多是最明晰的解决方案了。

2. 使用全局变量。这不是线程安全的方案，不推荐使用。

3. 传递一个可变（即可原地修改的） 对象：

       >>> def func2(a):
       ...     a[0] = 'new-value'     # 'a' references a mutable list
       ...     a[1] = a[1] + 1        # changes a shared object
       ...
       >>> args = ['old-value', 99]
       >>> func2(args)
       >>> args
       ['new-value', 100]

4. 传入一个接收可变对象的字典:

       >>> def func3(args):
       ...     args['a'] = 'new-value'     # args is a mutable dictionary
       ...     args['b'] = args['b'] + 1   # change it in-place
       ...
       >>> args = {'a': 'old-value', 'b': 99}
       >>> func3(args)
       >>> args
       {'a': 'new-value', 'b': 100}

5. 或者把值用类实例封装起来：

       >>> class Namespace:
       ...     def __init__(self, /, **args):
       ...         for key, value in args.items():
       ...             setattr(self, key, value)
       ...
       >>> def func4(args):
       ...     args.a = 'new-value'        # args is a mutable Namespace
       ...     args.b = args.b + 1         # change object in-place
       ...
       >>> args = Namespace(a='old-value', b=99)
       >>> func4(args)
       >>> vars(args)
       {'a': 'new-value', 'b': 100}

   没有什么理由要把问题搞得这么复杂。

最佳选择就是返回一个包含多个结果值的元组。

### 如何在 Python 中创建高阶函数？

有两种选择：嵌套作用域、可调用对象。假定需要定义 `linear(a,b)` ，其返回结果是一个计算出 `a*x+b` 的函数 `f(x)`。 采用嵌套作用域的方案如下：

    def linear(a, b):
        def result(x):
            return a * x + b
        return result

或者可采用可调用对象：

    class linear:

        def __init__(self, a, b):
            self.a, self.b = a, b

        def __call__(self, x):
            return self.a * x + self.b

采用这两种方案时：

    taxes = linear(0.3, 2)

都会得到一个可调用对象，可实现 `taxes(10e6) == 0.3 * 10e6 + 2` 。

可调用对象的方案有个缺点，就是速度稍慢且生成的代码略长。不过值得注意的是，同一组可调用对象能够通过继承来共享签名（类声明）：

    class exponential(linear):
        # __init__ inherited
        def __call__(self, x):
            return self.a * (x ** self.b)

对象可以为多个方法的运行状态进行封装：

    class counter:

        value = 0

        def set(self, x):
            self.value = x

        def up(self):
            self.value = self.value + 1

        def down(self):
            self.value = self.value - 1

    count = counter()
    inc, dec, reset = count.up, count.down, count.set

以上 `inc()` 、 `dec()` 和 `reset()` 的表现，就如同共享了同一计数变量一样。

### 如何复制 Python 对象？

一般情况下，用 `copy.copy()` 或 `copy.deepcopy()` 基本就可以了。并不是所有对象都支持复制，但多数是可以的。

某些对象可以用更简便的方法进行复制。比如字典对象就提供了 `copy()` 方法：

    newdict = olddict.copy()

序列可以用切片操作进行复制：

    new_l = l[:]

### 如何找到对象的方法或属性？

假定 x 是一个用户自定义类的实例，`dir(x)` 将返回一个按字母排序的名称列表，其中包含了实例的属性及由类定义的方法和属性。

### 如何用代码获取对象的名称？

一般而言这是无法实现的，因为对象并不存在真正的名称。赋值本质上是把某个名称绑定到某个值上；`def` 和 `class` 语句同样如此，只是值换成了某个可调用对象。比如以下代码：

    >>> class A:
    ...     pass
    ...
    >>> B = A
    >>> a = B()
    >>> b = a
    >>> print(b)
    <__main__.A object at 0x16D07CC>
    >>> print(a)
    <__main__.A object at 0x16D07CC>

可以不太严谨地说，上述类具有一个名称：即便它绑定了两个名称并通过名称 B 发起调用，可是创建出来的实例仍被视为是类 A 的实例。但无法说出实例的名称是 a 还是 b，因为这两个名称都被绑定到同一个值上了。

代码一般没有必要去“知晓”某个值的名称。通常这种需求预示着还是改变方案为好，除非真的是要编写内审程序。

在 comp.lang.python 中，Fredrik Lundh 在回答这样的问题时曾经给出过一个绝佳的类比：

> 这就像要知道家门口的那只猫的名字一样：猫（对象）自己不会说出它的名字，它根本就不在乎自己叫什么——所以唯一方法就是问一遍你所有的邻居（命名空间），这是不是他们家的猫（对象）……

> ……并且如果你发现它有很多名字或根本没有名字，那也不必惊讶！

### 逗号运算符的优先级是什么？

逗号不是 Python 的运算符。 请看以下例子：

    >>> "a" in "b", "a"
    (False, 'a')

由于逗号不是运算符，而只是表达式之间的分隔符，因此上述代码就相当于：

    ("a" in "b"), "a"

而不是：

    "a" in ("b", "a")

对于各种赋值运算符（ `=` 、 `+=` 等）来说同样如此。他们并不是真正的运算符，而只是赋值语句中的语法分隔符。

### 是否提供等价于 C 语言 "?:" 三目运算符的东西？

有的。语法如下：

    [on_true] if [expression] else [on_false]

    x, y = 50, 25
    small = x if x < y else y

在 Python 2.5 引入上述语法之前，通常的做法是使用逻辑运算符：

    [expression] and [on_true] or [on_false]

然而这种做法并不保险，因为当 on_true 为布尔值“假”时，结果将会出错。所以肯定还是采用 `... if ... else ...` 形式为妙。

### 是否可以用 Python 编写让人眼晕的单行程序?

可以。通常是在 `lambda` 中嵌套 `lambda` 来实现的。请参阅以下三个来自 Ulf Bartelt 的示例代码：

    from functools import reduce

    # Primes < 1000
    print(list(filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,
    map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000)))))

    # First 10 Fibonacci numbers
    print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1:
    f(x,f), range(10))))

    # Mandelbrot set
    print((lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+y,map(lambda y,
    Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM,
    Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro,
    i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y
    >=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr(
    64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy
    ))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24))
    #    \___ ___/  \___ ___/  |   |   |__ lines on screen
    #        V          V      |   |______ columns on screen
    #        |          |      |__________ maximum of "iterations"
    #        |          |_________________ range on y axis
    #        |____________________________ range on x axis

请不要在家里尝试，骚年！

### 函数形参列表中的斜杠（/）是什么意思？

函数参数列表中的斜杠表示在它之前的形参全都仅限位置形参。仅限位置形参没有可供外部使用的名称。在调用仅接受位置形参的函数时，实参只会根据位置映射到形参上。假定 `divmod()` 是一个仅接受位置形参的函数。 它的帮助文档如下所示：

    >>> help(divmod)
    Help on built-in function divmod in module builtins:

    divmod(x, y, /)
        Return the tuple (x//y, x%y).  Invariant: div*y + mod == x.

形参列表尾部的斜杠说明，两个形参都是仅限位置形参。因此，用关键字参数调用 `divmod()` 将会引发错误：

    >>> divmod(x=3, y=4)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: divmod() takes no keyword arguments

## 数字和字符串

### 如何给出十六进制和八进制整数？

要给出八进制数，需在八进制数值前面加上一个零和一个小写或大写字母 "o" 作为前缀。例如，要将变量 "a" 设为八进制的 "10" （十进制的 8），写法如下：

    >>> a = 0o10
    >>> a
    8

十六进制数也很简单。只要在十六进制数前面加上一个零和一个小写或大写的字母 "x"。十六进制数中的字母可以为大写或小写。比如在 Python 解释器中输入：

    >>> a = 0xa5
    >>> a
    165
    >>> b = 0XB2
    >>> b
    178

### 为什么 -22 // 10 会返回 -3 ？

这主要是为了让 `i % j` 的正负与 `j` 一致，如果期望如此，且期望如下等式成立：

    i == (i // j) * j + (i % j)

那么整除就必须返回向下取整的结果。C 语言同样要求保持这种一致性，于是编译器在截断 `i // j` 的结果时需要让 `i % j` 的正负与 `i` 一致。

对于 `i % j` 来说 `j` 为负值的应用场景实际上是非常少的。 而 `j` 为正值的情况则非常多，并且实际上在所有情况下让 `i % j` 的结果为 `>= 0` 会更有用处。 如果现在时间为 10 时，那么 200 小时前应是几时？ `-190 % 12 == 2` 是有用处的；`-190 % 12 == -10` 则是会导致意外的漏洞。

### 如何将字符串转换为数字？

对于整数，可使用内置的 `int()` 类型构造器，例如 `int('144') == 144`。 类似地，可使用 `float()` 转换为浮点数，例如 `float('144') == 144.0`。

默认情况下，这些操作会将数字按十进制来解读，因此 `int('0144') == 144` 为真值，而 `int('0x144')` 会引发 `ValueError`。 `int(string, base)` 接受第二个可选参数指定转换的基数，例如 `int( '0x144', 16) == 324`。 如果指定基数为 0，则按 Python 规则解读数字：前缀 '0o' 表示八进制，而 '0x' 表示十六进制。

如果只是想把字符串转为数字，请不要使用内置函数 `eval()`。 `eval()` 的速度慢很多且存在安全风险：别人可能会传入带有不良副作用的 Python 表达式。比如可能会传入 `__import__('os').system("rm -rf $HOME")` ，这会把 home 目录给删了。

`eval()` 还有把数字解析为 Python 表达式的后果，因此如 `eval('09')` 将会导致语法错误，因为 Python 不允许十进制数带有前导 '0'（'0' 除外）。

### 如何将数字转换为字符串？

比如要把数字 144 转换为字符串 '144'，可使用内置类型构造器 `str()`。如果要表示为十六进制或八进制数格式，可使用内置函数 `hex()` 或 `oct()`。更复杂的格式化方法请参阅 格式字符串字面值 和 格式字符串语法 等章节，比如 `"{:04d}".format(144)` 会生成 `'0144'` ， `"{:.3f}".format(1.0/3.0)` 则会生成 `'0.333'`。

### 如何修改字符串？

无法修改，因为字符串是不可变对象。 在大多数情况下，只要将各个部分组合起来构造出一个新字符串即可。如果需要一个能原地修改 Unicode 数据的对象，可以试试 `io.StringIO` 对象或 `array` 模块：

    >>> import io
    >>> s = "Hello, world"
    >>> sio = io.StringIO(s)
    >>> sio.getvalue()
    'Hello, world'
    >>> sio.seek(7)
    7
    >>> sio.write("there!")
    6
    >>> sio.getvalue()
    'Hello, there!'

    >>> import array
    >>> a = array.array('u', s)
    >>> print(a)
    array('u', 'Hello, world')
    >>> a[0] = 'y'
    >>> print(a)
    array('u', 'yello, world')
    >>> a.tounicode()
    'yello, world'

### 如何使用字符串调用函数/方法？

有多种技巧可供选择。

- 最好的做法是采用一个字典，将字符串映射为函数。其主要优势就是字符串不必与函数名一样。这也是用来模拟 case 结构的主要技巧：

      def a():
          pass

      def b():
          pass

      dispatch = {'go': a, 'stop': b}  # Note lack of parens for funcs

      dispatch[get_input()]()  # Note trailing parens to call function

- 利用内置函数 `getattr()` ：

      import foo
      getattr(foo, 'bar')()

  请注意 `getattr()` 可用于任何对象，包括类、类实例、模块等等。

  标准库就多次使用了这个技巧，例如：

      class Foo:
          def do_foo(self):
              ...

          def do_bar(self):
              ...

      f = getattr(foo_instance, 'do_' + opname)
      f()

- 用 `locals()` 解析出函数名：

      def myFunc():
          print("hello")

      fname = "myFunc"

      f = locals()[fname]
      f()

### 是否有与Perl 的chomp() 等效的方法，用于从字符串中删除尾随换行符？

可以使用 `S.rstrip("\r\n")` 从字符串 `S` 的末尾删除所有的换行符，而不删除其他尾随空格。如果字符串 `S` 表示多行，且末尾有几个空行，则将删除所有空行的换行符：

    >>> lines = ("line 1 \r\n"
    ...          "\r\n"
    ...          "\r\n")
    >>> lines.rstrip("\n\r")
    'line 1 '

由于通常只在一次读取一行文本时才需要这样做，所以使用 `S.rstrip()` 这种方式工作得很好。

### 是否有 scanf() 或 sscanf() 的等价函数？

没有。

如果要对简单的输入进行解析，最容易的做法通常是利用字符串对象的 `split()` 方法将一行按空白符分隔拆分为多个单词，然后用 `int()` 或 `float()` 将十进制数字符串转换为数字值。 `split()` 支持可选的 "sep" 形参，适用于分隔符不用空白符的情况。

如果要对更复杂的输入进行解析，那么正则表达式要比 C 语言的 `sscanf()` 更强大，也更合适。

### 'UnicodeDecodeError' 或 'UnicodeEncodeError' 错误是什么意思？

见 Unicode 指南

## 性能

### 我的程序太慢了。该如何加快速度？

总的来说，这是个棘手的问题。在进一步讨论之前，首先应该记住以下几件事：

- 不同的 Python 实现具有不同的性能特点。 本 FAQ 着重解答的是 CPython。

- 不同操作系统可能会有不同表现，尤其是涉及 I/O 和多线程时。

- 在尝试优化代码 之前 ，务必要先找出程序中的热点（请参阅 `profile` 模块）。

- 编写基准测试脚本，在寻求性能提升的过程中就能实现快速迭代（请参阅 `timeit` 模块）。

- 强烈建议首先要保证足够高的代码测试覆盖率（通过单元测试或其他技术），因为复杂的优化有可能会导致代码回退。

话虽如此，Python 代码的提速还是有很多技巧的。以下列出了一些普适性的原则，对于让性能达到可接受的水平会有很大帮助：

- 相较于试图对全部代码铺开做微观优化，优化算法（或换用更快的算法）可以产出更大的收益。

- 使用正确的数据结构。参考 內建型態 和 `collections` 模块的文档。

- 如果标准库已为某些操作提供了基础函数，则可能（当然不能保证）比所有自编的函数都要快。对于用 C 语言编写的基础函数则更是如此，比如内置函数和一些扩展类型。例如，一定要用内置方法 `list.sort()` 或 `sorted()` 函数进行排序（某些高级用法的示例请参阅 如何排序 ）。

- 抽象往往会造成中间层，并会迫使解释器执行更多的操作。如果抽象出来的中间层级太多，工作量超过了要完成的有效任务，那么程序就会被拖慢。应该避免过度的抽象，而且往往也会对可读性产生不利影响，特别是当函数或方法比较小的时候。

如果你已经达到纯 Python 允许的限制，那么有一些工具可以让你走得更远。 例如， Cython 可以将稍微修改的 Python 代码版本编译为 C 扩展，并且可以在许多不同的平台上使用。 Cython 可以利用编译（和可选的类型注释）来使代码明显快于解释运行时的速度。 如果您对 C 编程技能有信心，也可以自己 编写 C 扩展模块 。

也參考

专门介绍 性能提示 的wiki页面。

### 将多个字符串连接在一起的最有效方法是什么？

`str` 和 `bytes` 对象是不可变的，因此连接多个字符串的效率会很低，因为每次连接都会创建一个新的对象。一般情况下，总耗时与字符串总长是二次方的关系。

如果要连接多个 `str` 对象，通常推荐的方案是先全部放入列表，最后再调用 `str.join()` ：

    chunks = []
    for s in my_strings:
        chunks.append(s)
    result = ''.join(chunks)

（还有一种合理高效的习惯做法，就是利用 `io.StringIO` ）

如果要连接多个 `bytes` 对象，推荐做法是用 `bytearray` 对象的原地连接操作（ `+=` 运算符）追加数据：

    result = bytearray()
    for b in my_bytes_objects:
        result += b

## 序列（元组/列表）

### 如何在元组和列表之间进行转换？

类型构造器 `tuple(seq)` 可将任意序列（实际上是任意可迭代对象）转换为数据项和顺序均不变的元组。

例如，`tuple([1, 2, 3])` 会生成 `(1, 2, 3)` ， `tuple('abc')` 则会生成 `('a', 'b', 'c')` 。 如果参数就是元组，则不会创建副本而是返回同一对象，因此如果无法确定某个对象是否为元组时，直接调用 `tuple()` 也没什么代价。

类型构造器 `list(seq)` 可将任意序列或可迭代对象转换为数据项和顺序均不变的列表。例如，`list((1, 2, 3))` 会生成 `[1, 2, 3]` 而 `list('abc')` 则会生成 `['a', 'b', 'c']`。如果参数即为列表，则会像 `seq[:]` 那样创建一个副本。

### 什么是负数索引？

Python 序列的索引可以是正数或负数。索引为正数时，0 是第一个索引值， 1 为第二个，依此类推。索引为负数时，-1 为倒数第一个索引值，-2 为倒数第二个，依此类推。可以认为 `seq[-n]` 就相当于 `seq[len(seq)-n]`。

使用负数序号有时会很方便。 例如 `S[:-1]` 就是原字符串去掉最后一个字符，这可以用来移除某个字符串末尾的换行符。

### 序列如何以逆序遍历？

使用内置函数 `reversed()` ：

    for x in reversed(sequence):
        ...  # do something with x ...

原序列不会变化，而是构建一个逆序的新副本以供遍历。

### 如何从列表中删除重复项？

许多完成此操作的的详细介绍，可参阅 Python Cookbook：

> https://code.activestate.com/recipes/52560/

如果列表允许重新排序，不妨先对其排序，然后从列表末尾开始扫描，依次删除重复项：

    if mylist:
        mylist.sort()
        last = mylist[-1]
        for i in range(len(mylist)-2, -1, -1):
            if last == mylist[i]:
                del mylist[i]
            else:
                last = mylist[i]

如果列表的所有元素都能用作集合的键（即都是 hashable ），以下做法速度往往更快：

    mylist = list(set(mylist))

以上操作会将列表转换为集合，从而删除重复项，然后返回成列表。

### 如何从列表中删除多个项？

类似于删除重复项，一种做法是反向遍历并根据条件删除。不过更简单快速的做法就是切片替换操作，采用隐式或显式的正向迭代遍历。以下是三种变体写法：

    mylist[:] = filter(keep_function, mylist)
    mylist[:] = (x for x in mylist if keep_condition)
    mylist[:] = [x for x in mylist if keep_condition]

列表推导式可能是最快的。

### 如何在 Python 中创建数组？

用列表：

    ["this", 1, "is", "an", "array"]

列表在时间复杂度方面相当于 C 或 Pascal 的数组；主要区别在于，Python 列表可以包含多种不同类型的对象。

`array` 模块也提供了一些创建数组的方法，数组元素得是紧凑格式的固定类型，但索引访问的速度比列表慢。还要注意，Numeric 等扩展模块也定义了一些各具特性的类数组结构。

若要得到 Lisp 风格的列表，可以用元组模拟 cons 元素：

    lisp_list = ("like",  ("this",  ("example", None) ) )

若要具备可变性，可以不用元组而是用列表。模拟 lisp car 函数的是 `lisp_list[0]` ，模拟 cdr 函数的是 `lisp_list[1]` 。仅当真正必要时才会这么用，因为通常这种用法要比 Python 列表慢得多。

### 如何创建多维列表？

多维数组或许会用以下方式建立：

    >>> A = [[None] * 2] * 3

打印出来貌似没错：

    >>> A
    [[None, None], [None, None], [None, None]]

但如果给某一项赋值，结果会同时在多个位置体现出来：

    >>> A[0][0] = 5
    >>> A
    [[5, None], [5, None], [5, None]]

原因在于用 `*` 对列表执行重复操作并不会创建副本，而只是创建现有对象的引用。 `*3` 创建的是包含 3 个引用的列表，每个引用指向的是同一个长度为 2 的列表。1 处改动会体现在所有地方，这一定不是应有的方案。

推荐做法是先创建一个所需长度的列表，然后将每个元素都填充为一个新建列表。

    A = [None] * 3
    for i in range(3):
        A[i] = [None] * 2

以上生成了一个包含 3 个列表的列表，每个子列表的长度为 2。也可以采用列表推导式：

    w, h = 2, 3
    A = [[None] * w for i in range(h)]

或者你还可以使用提供矩阵类型的扩展包；其中最著名的是 NumPy。

### 如何将方法应用于一系列对象？

可以使用列表推导式：

    result = [obj.method() for obj in mylist]

### 为什么 a_tuple[i] += ['item'] 会引发异常？

这是由两个因素共同导致的，一是增强赋值运算符属于 赋值 运算符，二是 Python 可变和不可变对象之间的差别。

只要元组的元素指向可变对象，这时对元素进行增强赋值，那么这里介绍的内容都是适用的。在此只以 `list` 和 `+=` 举例。

如果你写成这样:

    >>> a_tuple = (1, 2)
    >>> a_tuple[0] += 1
    Traceback (most recent call last):
       ...
    TypeError: 'tuple' object does not support item assignment

触发异常的原因显而易见： `1` 会与指向（`1`）的对象 `a_tuple[0]` 相加，生成结果对象 `2`，但在试图将运算结果 `2` 赋值给元组的 `0` 号元素时就会报错，因为元组元素的指向无法更改。

其实在幕后，上述增强赋值语句的执行过程大致如下：

    >>> result = a_tuple[0] + 1
    >>> a_tuple[0] = result
    Traceback (most recent call last):
      ...
    TypeError: 'tuple' object does not support item assignment

由于元组是不可变的，因此赋值这步会引发错误。

如果写成以下这样：

    >>> a_tuple = (['foo'], 'bar')
    >>> a_tuple[0] += ['item']
    Traceback (most recent call last):
      ...
    TypeError: 'tuple' object does not support item assignment

这时触发异常会令人略感惊讶，更让人吃惊的是虽有报错，但加法操作却生效了：

    >>> a_tuple[0]
    ['foo', 'item']

要明白为何会这样，需要知道 (a) 如果一个对象实现了 `__iadd__` 魔法方法，在执行 `+=` 增强赋值时就会调用它，并采纳其返回值；(b) 对于列表而言，`__iadd__` 相当于在列表上调用 `extend` 并返回该列表。因此对于列表可以说 `+=` 就是 `list.extend` 的“快捷方式”：

    >>> a_list = []
    >>> a_list += [1]
    >>> a_list
    [1]

这相当于：

    >>> result = a_list.__iadd__([1])
    >>> a_list = result

a_list 所引用的对象已被修改，而引用被修改对象的指针又重新被赋值给 `a_list`。 赋值的最终结果没有变化，因为它是引用 `a_list` 之前所引用的同一对象的指针，但仍然发生了赋值操作。

因此，在此元组示例中，发生的事情等同于：

    >>> result = a_tuple[0].__iadd__(['item'])
    >>> a_tuple[0] = result
    Traceback (most recent call last):
      ...
    TypeError: 'tuple' object does not support item assignment

`__iadd__` 成功执行，因此列表得到了扩充，但是虽然 `result` 指向了 `a_tuple[0]` 已经指向的同一对象，最后的赋值仍然导致了报错，因为元组是不可变的。

### 我想做一个复杂的排序：能用 Python 进行施瓦茨变换吗？

归功于 Perl 社区的 Randal Schwartz，该技术根据度量值对列表进行排序，该度量值将每个元素映射为“顺序值”。在 Python 中，请利用 `list.sort()` 方法的 `key` 参数：

    Isorted = L[:]
    Isorted.sort(key=lambda s: int(s[10:15]))

### 如何根据另一个列表的值对某列表进行排序？

将它们合并到元组的迭代器中，对结果列表进行排序，然后选择所需的元素。

    >>> list1 = ["what", "I'm", "sorting", "by"]
    >>> list2 = ["something", "else", "to", "sort"]
    >>> pairs = zip(list1, list2)
    >>> pairs = sorted(pairs)
    >>> pairs
    [("I'm", 'else'), ('by', 'sort'), ('sorting', 'to'), ('what', 'something')]
    >>> result = [x[1] for x in pairs]
    >>> result
    ['else', 'sort', 'to', 'something']

## 对象

### 什么是类？

类是通过执行 class 语句创建的某种对象的类型。创建实例对象时，用 Class 对象作为模板，实例对象既包含了数据（属性），又包含了数据类型特有的代码（方法）。

类可以基于一个或多个其他类（称之为基类）进行创建。基类的属性和方法都得以继承。这样对象模型就可以通过继承不断地进行细化。比如通用的 `Mailbox` 类提供了邮箱的基本访问方法.，它的子类 `MboxMailbox`、 `MaildirMailbox`、 `OutlookMailbox` 则能够处理各种特定的邮箱格式。

### 什么是方法？

方法是属于对象的函数，对于对象 `x` ，通常以 `x.name(arguments...)` 的形式调用。方法以函数的形式给出定义，位于类的定义内：

    class C:
        def meth(self, arg):
            return arg * 2 + self.attribute

### 什么是 self ？

Self 只是方法的第一个参数的习惯性名称。假定某个类中有个方法定义为 `meth(self, a, b, c)` ，则其实例 `x` 应以 `x.meth(a, b, c)` 的形式进行调用；而被调用的方法则应视其为做了 `meth(x, a, b, c)` 形式的调用。

另请参阅 为什么必须在方法定义和调用中显式使用“self”？ 。

### 如何检查对象是否为给定类或其子类的一个实例？

可使用内置函数 `isinstance(obj, cls)`。可以检测对象是否属于多个类中某一个的实例，只要把单个类换成元组即可，比如 `isinstance(obj, (class1, class2, ...))`，还可以检查对象是否属于某个 Python 内置类型，例如 `isinstance(obj, str)` 或 `isinstance(obj, (int, float, complex))`。

请注意 `isinstance()` 还会检测派生自 abstract base class 的虚继承。 因此对于已注册的类，即便没有直接或间接继承自抽象基类，对抽象基类的检测都将返回 `True` 。要想检测“真正的继承”，请扫描类的 MRO:

    from collections.abc import Mapping

    class P:
         pass

    class C(P):
        pass

    Mapping.register(P)

    >>> c = C()
    >>> isinstance(c, C)        # direct
    True
    >>> isinstance(c, P)        # indirect
    True
    >>> isinstance(c, Mapping)  # virtual
    True

    # Actual inheritance chain
    >>> type(c).__mro__
    (<class 'C'>, <class 'P'>, <class 'object'>)

    # Test for "true inheritance"
    >>> Mapping in type(c).__mro__
    False

请注意，大多数程序不会经常用 `isinstance()` 对用户自定义类进行检测。 如果是自已开发的类，更合适的面向对象编程风格应该是在类中定义多种方法，以封装特定的行为，而不是检查对象属于什么类再据此干不同的事。假定有如下执行某些操作的函数:

    def search(obj):
        if isinstance(obj, Mailbox):
            ...  # code to search a mailbox
        elif isinstance(obj, Document):
            ...  # code to search a document
        elif ...

更好的方法是在所有类上定义一个 `search()` 方法，然后调用它：

    class Mailbox:
        def search(self):
            ...  # code to search a mailbox

    class Document:
        def search(self):
            ...  # code to search a document

    obj.search()

### 什么是委托？

委托是一种面向对象的技术（也称为设计模式）。假设对象 `x` 已经存在，现在想要改变其某个方法的行为。可以创建一个新类，其中提供了所需修改方法的新实现，而将所有其他方法都委托给 `x` 的对应方法。

Python 程序员可以轻松实现委托。比如以下实现了一个类似于文件的类，只是会把所有写入的数据转换为大写：

    class UpperOut:

        def __init__(self, outfile):
            self._outfile = outfile

        def write(self, s):
            self._outfile.write(s.upper())

        def __getattr__(self, name):
            return getattr(self._outfile, name)

这里 `UpperOut` 类重新定义了 `write()` 方法，在调用下层的 `self._outfile.write()` 方法之前，会将参数字符串转换为大写。其他所有方法则都被委托给下层的 `self._outfile` 对象。委托是通过 `__getattr__` 方法完成的；请参阅 语言参考 了解有关控制属性访问的更多信息。

请注意，更常见情况下，委托可能会变得比较棘手。如果属性既需要写入又需要读取，那么类还必须定义 `__setattr__()` 方法，而这时就必须十分的小心。基础的 `__setattr__()` 实现代码大致如下：

    class X:
        ...
        def __setattr__(self, name, value):
            self.__dict__[name] = value
        ...

大多数 `__setattr__()` 实现必须修改 `self.__dict__` 来为自身保存局部状态，而不至于引起无限递归。

### 如何在派生类中调用被重载的基类方法?

使用内置的 `super()` 函数：

    class Derived(Base):
        def meth(self):
            super(Derived, self).meth()

如果是 Python 3.0 之前的版本，可能用的还是传统类：对于诸如 `class Derived(Base): ...` 之类的类定义，可以用 `Base.meth(self, arguments...)` 的形式调用 `Base` (或 `Base` 的某个基类）中定义的方法 `meth()` 。这里， `Base.meth` 是一个未绑定的方法，因此需要给出 `self` 参数。

### 如何让代码更容易对基类进行修改？

可以为基类赋一个别名并基于该别名进行派生。这样只要修改赋给该别名的值即可。顺便提一下，如要动态地确定（例如根据可用的资源）该使用哪个基类，这个技巧也非常方便。例如：

    class Base:
        ...

    BaseAlias = Base

    class Derived(BaseAlias):
        ...

### 如何创建静态类数据和静态类方法？

Python 支持静态数据和静态方法（以 C++ 或 Java 的定义而言）。

静态数据只需定义一个类属性即可。若要为属性赋新值，则必须在赋值时显式使用类名：

    class C:
        count = 0   # number of times C.__init__ called

        def __init__(self):
            C.count = C.count + 1

        def getcount(self):
            return C.count  # or return self.count

对于所有符合 `isinstance(c, C)` 的 `c`， `c.count` 也同样指向 `C.count` ，除非被 `c` 自身重载，或者被从 `c.__class__` 回溯到基类 `C` 的搜索路径上的某个类所重载。

注意：在 C 的某个方法内部，像 `self.count = 42` 这样的赋值将在 `self` 自身的字典中新建一个名为 "count" 的不相关实例。 想要重新绑定类静态数据名称就必须总是指明类名，无论是在方法内部还是外部:

    C.count = 314

Python 支持静态方法：

    class C:
        @staticmethod
        def static(arg1, arg2, arg3):
            # No 'self' parameter!
            ...

不过为了获得静态方法的效果，还有一种做法直接得多，也即使用模块级函数即可：

    def getcount():
        return C.count

如果代码的结构化比较充分，每个模块只定义了一个类（或者多个类的层次关系密切相关），那就具备了应有的封装。

### 在 Python 中如何重载构造函数（或方法）？

这个答案实际上适用于所有方法，但问题通常首先出现于构造函数的应用场景中。

在 C++ 中，代码会如下所示：

    class C {
        C() { cout << "No arguments\n"; }
        C(int i) { cout << "Argument is " << i << "\n"; }
    }

在 Python 中，只能编写一个构造函数，并用默认参数捕获所有情况。例如：

    class C:
        def __init__(self, i=None):
            if i is None:
                print("No arguments")
            else:
                print("Argument is", i)

这不完全等同，但在实践中足够接近。

也可以试试采用变长参数列表，例如：

    def __init__(self, *args):
        ...

上述做法同样适用于所有方法定义。

### 在用 __spam 的时候得到一个类似 _SomeClassName__spam 的错误信息。

以双下划线打头的变量名会被“破坏”，以便以一种简单高效的方式定义类私有变量。任何形式为 `__spam` 的标识符（至少前缀两个下划线，至多后缀一个下划线）文本均会被替换为 `_classname__spam`，其中 `classname` 为去除了全部前缀下划线的当前类名称。

这并不能保证私密性：外部用户仍然可以访问 "_classname__spam" 属性，私有变量值也在对象的 `__dict__` 中可见。 许多 Python 程序员根本不操心要去使用私有变量名。

### 类定义了 __del__ 方法，但是删除对象时没有调用它。

这有几个可能的原因。

del 语句不一定调用 `__del__()` —— 它只是减少对象的引用计数，如果（引用计数）达到零，才会调用 `__del__()`。

如果数据结构包含循环链接（比如树的每个子节点都带有父节点的引用，而每个父节点也带有子节点的列表），则引用计数永远不会回零。尽管 Python 偶尔会用某种算法检测这种循环引用，但在数据结构的最后一条引用消失之后，垃圾收集器可能还要过段时间才会运行，因此 `__del__()` 方法可能会在不方便和随机的时刻被调用。这对于重现一个问题，是非常不方便的。更糟糕的是，各个对象的 `__del__()` 方法是以随机顺序执行的。虽然可以运行 `gc.collect()` 来强制执行垃圾回收工作，但 仍会存在 一些对象永远不会被回收的失控情况。

尽管有垃圾回收器的存在，但为对象定义显式的 `close()` 方法，只要一用完即可供调用，这依然是一个好主意。这样 `close()` 方法即可删除引用子对象的属性。请勿直接调用 `__del__()` —— 而 `__del__()` 应该调用 `close()`，并且应能确保可以对同一对象多次调用 `close()` 。

另一种避免循环引用的做法是利用 `weakref` 模块，该模块允许指向对象但不增加其引用计数。例如，树状数据结构应该对父节点和同级节点使用弱引用（如果真要用的话！）

最后提一下，如果 `__del__()` 方法引发了异常，会将警告消息打印到 `sys.stderr` 。

### 如何获取给定类的所有实例的列表？

Python 不会记录类（或内置类型）的实例。可以在类的构造函数中编写代码，通过保留每个实例的弱引用列表来跟踪所有实例。

### 为什么 `id()` 的结果看起来不是唯一的？

`id()` 返回一个整数，该整数在对象的生命周期内保证是唯一的。 因为在 CPython 中，这是对象的内存地址，所以经常发生在从内存中删除对象之后，下一个新创建的对象被分配在内存中的相同位置。 这个例子说明了这一点：

    >>> id(1000)
    13901272
    >>> id(2000)
    13901272

这两个 id 属于不同的整数对象，之前先创建了对象，执行 `id()` 调用后又立即被删除了。若要确保检测 id 时的对象仍处于活动状态，请再创建一个对该对象的引用：

    >>> a = 1000; b = 2000
    >>> id(a)
    13901272
    >>> id(b)
    13891296

### 什么情况下可以依靠 is 运算符进行对象的身份相等性测试？

`is` 运算符可用于测试对象的身份相等性。`a is b` 等价于 `id(a) == id(b)`。

身份相等性最重要的特性就是对象总是等同于自身，`a is a` 一定返回 `True`。身份相等性测试的速度通常比相等性测试要快。而且与相等性测试不一样，身份相等性测试会确保返回布尔值 `True` 或 `False`。

但是，身份相等性测试 只能 在对象身份确定的场景下才可替代相等性测试。一般来说，有以下3种情况对象身份是可以确定的：

1） 赋值操作创建了新的名称但没有改变对象身份。 在赋值操作 `new = old` 之后，可以保证 `new is old`。

2） 将对象置入存放对象引用的容器，对象身份不会改变。在列表赋值操作 `s[0] = x` 之后，可以保证 `s[0] is x`。

3） 单例对象，也即该对象只能存在一个实例。在赋值操作 `a = None` 和 `b = None` 之后，可以保证 `a is b`，因为 `None` 是单例对象。

其他大多数情况下，都不建议使用身份相等性测试，而应采用相等性测试。尤其是不应将身份相等性测试用于检测常量值，例如 `int` 和 `str`，因为他们并不一定是单例对象：

    >>> a = 1000
    >>> b = 500
    >>> c = b + 500
    >>> a is c
    False

    >>> a = 'Python'
    >>> b = 'Py'
    >>> c = b + 'thon'
    >>> a is c
    False

同样地，可变容器的新实例，对象身份一定不同：

    >>> a = []
    >>> b = []
    >>> a is b
    False

在标准库代码中，给出了一些正确使用对象身份测试的常见模式：

1） 正如 PEP 8 所推荐的，对象身份测试是 `None` 值的推荐检测方式。这样的代码读起来就像自然的英文，并可以避免与其他可能为布尔值且计算结果为 False 的对象相混淆。

2） 如果 `None` 也是有效的输入值，则可选参数的检测就可能比较棘手。 这时可以创建一个单例哨兵对象，要确保与其他对象都不相同。例如，下面是一个类似 `dict.pop()` 的方法：

    _sentinel = object()

    def pop(self, key, default=_sentinel):
        if key in self:
            value = self[key]
            del self[key]
            return value
        if default is _sentinel:
            raise KeyError(key)
        return default

3） 编写容器的实现代码时，有时需要用对象身份测试来加强相等性检测。这样代码就不会被 `float('NaN')` 这类与自身不相等的对象所干扰。

例如，以下是 `collections.abc.Sequence.__contains__()` 的实现代码：

    def __contains__(self, value):
        for v in self:
            if v is value or v == value:
                return True
        return False

### How can a subclass control what data is stored in an immutable instance?

When subclassing an immutable type, override the `__new__()` method instead of the `__init__()` method. The latter only runs after an instance is created, which is too late to alter data in an immutable instance.

All of these immutable classes have a different signature than their parent class:

    from datetime import date

    class FirstOfMonthDate(date):
        "Always choose the first day of the month"
        def __new__(cls, year, month, day):
            return super().__new__(cls, year, month, 1)

    class NamedInt(int):
        "Allow text names for some numbers"
        xlat = {'zero': 0, 'one': 1, 'ten': 10}
        def __new__(cls, value):
            value = cls.xlat.get(value, value)
            return super().__new__(cls, value)

    class TitleStr(str):
        "Convert str to name suitable for a URL path"
        def __new__(cls, s):
            s = s.lower().replace(' ', '-')
            s = ''.join([c for c in s if c.isalnum() or c == '-'])
            return super().__new__(cls, s)

The classes can be used like this:

    >>> FirstOfMonthDate(2012, 2, 14)
    FirstOfMonthDate(2012, 2, 1)
    >>> NamedInt('ten')
    10
    >>> NamedInt(20)
    20
    >>> TitleStr('Blog: Why Python Rocks')
    'blog-why-python-rocks'

## 模块

### 如何创建 .pyc 文件？

当首次导入模块时（或当前已编译文件创建之后源文件发生了改动），在``.py``文件所在目录的``__pycache__``子目录下会创建一个包含已编译代码的``.pyc``文件。该``.pyc``文件的名称开头部分将与``.py``文件名相同，并以``.pyc``为后缀，中间部分则依据创建它的``python``版本而各不相同。（详见 PEP 3147。）

`.pyc` 文件有可能会无法创建，原因之一是源码文件所在的目录存在权限问题，这样就无法创建 `__pycache__` 子目录。假如以某个用户开发程序而以另一用户运行程序，就有可能发生权限问题，测试 Web 服务器就属于这种情况。

除非设置了 `PYTHONDONTWRITEBYTECODE` 环境变量，否则导入模块并且 Python 能够创建``__pycache__``子目录并把已编译模块写入该子目录（权限、存储空间等等）时，.pyc 文件就将自动创建。

在最高层级运行的 Python 脚本不会被视为经过了导入操作，因此不会创建 `.pyc` 文件。假定有一个最高层级的模块文件 `foo.py`，它导入了另一个模块 `xyz.py`，当运行 `foo` 模块（通过输入 shell 命令 `python foo.py` ），则会为 `xyz` 创建一个 `.pyc`，因为 `xyz` 是被导入的，但不会为 `foo` 创建 `.pyc` 文件，因为 `foo.py` 不是被导入的。

若要为 `foo` 创建 `.pyc` 文件 —— 即为未做导入的模块创建 `.pyc` 文件 —— 可以利用 `py_compile` 和 `compileall` 模块。

`py_compile` 模块能够手动编译任意模块。 一种做法是交互式地使用该模块中的 `compile()` 函数:

    >>> import py_compile
    >>> py_compile.compile('foo.py')

这将会将 `.pyc` 文件写入与 `foo.py` 相同位置下的 `__pycache__` 子目录（或者你也可以通过可选参数 `cfile` 来重载该行为）。

还可以用 `compileall` 模块自动编译一个或多个目录下的所有文件。只要在命令行提示符中运行 `compileall.py` 并给出要编译的 Python 文件所在目录路径即可：

    python -m compileall .

### 如何找到当前模块名称？

模块可以查看预定义的全局变量 `__name__` 获悉自己的名称。如其值为 `'__main__'` ，程序将作为脚本运行。通常，许多通过导入使用的模块同时也提供命令行接口或自检代码，这些代码只在检测到处于 `__name__` 之后才会执行：

    def main():
        print('Running test...')
        ...

    if __name__ == '__main__':
        main()

### 如何让模块相互导入？

假设有以下模块：

foo.py：

    from bar import bar_var
    foo_var = 1

bar.py：

    from foo import foo_var
    bar_var = 2

问题是解释器将执行以下步骤：

- 首先导入foo

- 创建用于foo的空全局变量

- foo被编译并开始执行

- foo 导入 bar

- 创建了用于bar 的空全局变量

- bar被编译并开始执行

- bar 导入 foo（这是一个空操作，因为已经有一个名为 foo 的模块）。

- bar.foo_var = foo.foo_var

最后一步失败了，因为 Python 还没有完成对 foo 的解释，foo 的全局符号字典仍然是空的。

当你使用 `import foo` ，然后尝试在全局代码中访问 `foo.foo_var` 时，会发生同样的事情。

这个问题有（至少）三种可能的解决方法。

Guido van Rossum 建议完全避免使用 `from <module> import ...` ，并将所有代码放在函数中。全局变量和类变量的初始化只应使用常量或内置函数。这意味着导入模块中的所有内容都以 `<module>.<name>` 的形式引用。

Jim Roskind 建议每个模块都应遵循以下顺序：

- 导出（全局变量、函数和不需要导入基类的类）

- `import` 语句

- 本模块的功能代码（包括根据导入值进行初始化的全局变量）。

van Rossum 不喜欢以上做法，因为 import 语句出现在了奇怪的地方，但确实有效。

Matthias Urlichs 建议对代码进行重构，使得递归导入根本就没必要发生。

这些解决方案并不相互排斥。

### __import__('x.y.z') 返回的是 <module 'x'> ；该如何得到 z 呢？

不妨考虑换用 `importlib` 中的函数 `import_module()` ：

    z = importlib.import_module('x.y.z')

### 对已导入的模块进行了编辑并重新导入，但变动没有得以体现。这是为什么？

出于效率和一致性的原因，Python 仅在第一次导入模块时读取模块文件。否则，在一个多模块的程序中，每个模块都会导入相同的基础模块，那么基础模块将会被一而再、再而三地解析。如果要强行重新读取已更改的模块，请执行以下操作：

    import importlib
    import modname
    importlib.reload(modname)

警告：这种技术并非万无一失。尤其是模块包含了以下语句时：

    from modname import some_objects

仍将继续使用前一版的导入对象。如果模块包含了类的定义，并 不会 用新的类定义更新现有的类实例。这样可能会导致以下矛盾的行为：

    >>> import importlib
    >>> import cls
    >>> c = cls.C()                # Create an instance of C
    >>> importlib.reload(cls)
    <module 'cls' from 'cls.py'>
    >>> isinstance(c, cls.C)       # isinstance is false?!?
    False

只要把类对象的 id 打印出来，问题的性质就会一目了然：

    >>> hex(id(c.__class__))
    '0x7352a0'
    >>> hex(id(cls.C))
    '0x4198d0'