• 為什麼當變數有值時我得到錯誤訊息 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() 的结果看起来不是唯一的?

  • 如何创建 .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 二进制程序捆绑在一起生成单个可执行文件。

    一种是使用冻结工具,它包含在Python源代码树 Tools/freeze 中。它将Python字节代码转换为C数组;一个C编译器,你可以将所有模块嵌入到一个新程序中,然后将其与标准Python模块链接。

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

    显然,freeze 需要一个 C 编译器。 还有一些其他实用工具则不需要:

  • py2exe 用于生成 Windows 版二进制可执行文件

  • py2app 用于生成 Mac OS X 版二进制可执行文件

  • cx_Freeze 用于生成跨平台的二进制可执行文件

  • 是否有 Python 编码标准或风格指南?

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

    语言核心内容

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

    通过在函数体中的某处添加赋值语句,导致以前正常工作的代码被修改而得到 UnboundLocalError 会令人感到意外。

    這段程式碼:

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

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

    >>> 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()
    

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

    >>> print(x)
    

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

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

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

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

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

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

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

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

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

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

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

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

    为了避免发生上述情况,需要将值保存在 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]()
    >>> squares[4]()
    

    请注意,上述表现并不是 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)
    

    请注意,出于同样的原因,使用模块也是实现Singleton设计模式的基础。

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

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

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

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

  • 标准库模块 -- 例如: sys, os, getopt, re

  • 第三方库模块(安装在Python的site-packages目录中的任何内容) -- 例如mx.DateTime,ZODB,PIL.Image等

  • 本地开发的模块

  • 为了避免循环导入引发的问题,有时需要将模块导入语句移入函数或类的内部。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):
    

    foobarkwargsfunc 的形参。 不过在调用 func 时,例如:

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

    42314somevar 则是实参。

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

    如果代码编写如下:

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

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

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

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

  • 列表属于 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
    

    可见这时 xy 就不再相等了。因为整数是 immutable 对象,在执行 x = x + 1 时,并不会修改整数对象 5,给它加上 1;而是创建了一个新的对象(整数对象 6 )并将其赋给 x (也就是改变了 x 所指向的对象)。在赋值完成后,就有了两个对象(整数对象 65 )和分别指向他俩的两个变量( x 现在指向 6y 仍然指向 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 则会创建新的对象)。

    换而言之:

  • 对于一个可变对象( listdictset 等等),可以利用某些特定的操作进行修改,所有引用它的变量都会反映出改动情况。

  • 对于一个不可变对象( strinttuple 等),所有引用它的变量都会给出相同的值,但所有改变其值的操作都将返回一个新的对象。

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

    如何编写带有输出参数的函数(按照引用调用)?

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

  • 返回一个元组:

    >>> 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)
    

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

  • 使用全局变量。这不是线程安全的方案,不推荐使用。

  • 传递一个可变(即可原地修改的) 对象:

    >>> 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)
    ['new-value', 100]
    
  • 传入一个接收可变对象的字典:

    >>> 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)
    {'a': 'new-value', 'b': 100}
    
  • 或者把值用类实例封装起来:

    >>> 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[:]
    

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

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

    >>> 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
    

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

    >>> a = 0xa5
    >>> b = 0XB2
    

    为什么 -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') == 144int('0x144') 会引发 ValueErrorint(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)
    >>> sio.write("there!")
    >>> 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'
    

    是否有与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页面。

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

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

    如果要连接多个 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 模块还提供了创建具有紧凑表示的固定类型的数组的方法,但它的索引速度比列表慢。还要注意,数字扩展和其他扩展还定义了具有各种特性的类似数组的结构。

    要获取Lisp样式的列表,可以使用元组模拟cons单元:

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

    如果需要可变性,可以使用列表而不是元组。这里模拟lisp car的是 lisp_list[0] ,模拟cdr的是 lisp_list[1] 。只有在你确定真的需要的时候才这样做,因为它通常比使用Python列表慢得多。

    如何创建多维列表?

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

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

    打印出来貌似没错:

    [[None, None], [None, None], [None, None]]

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

    >>> A[0][0] = 5
    [[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

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

    可以使用列表推导式:

  •