Programming FAQ

Contents

General Questions

是否有带有断点,单步执行等Function的源代码级调试器?

Yes.

pdb 模块是一个简单但适用于 Python 的控制台模式调试器。它是标准 Python 库的一部分,并且是库参考手册中记录的内容。您也可以使用 pdb 的代码作为示例来编写自己的调试器。

IDLE 交互式开发环境是标准 Python 发行版的一部分(通常以 Tools/scripts/idle 形式提供),其中包括一个图形调试器。

PythonWin 是一个 Python IDE,其中包括基于 pdb 的 GUI 调试器。 Pythonwin 调试器为断点着色,并具有许多很酷的Function,例如调试非 Pythonwin 程序。 Pythonwin 是适用于 Windows 的 Python 扩展项目的一部分,也是 ActivePython 发行版的一部分(请参见https://www.activestate.com/activepython)。

Boa Constructor是使用 wxWidgets 的 IDE 和 GUI 生成器。它提供可视化的框架创建和操作,对象检查器,源代码上的许多视图,例如对象浏览器,继承层次结构,文档字符串生成的 html 文档,高级调试器,集成帮助和 Zope 支持。

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

Pydb 是标准 Python 调试器 pdb 的版本,已修改为与流行的图形调试器前端 DDD(数据显示调试器)一起使用。 Pydb 可以在http://bashdb.sourceforge.net/pydb/处找到,DDD 可以在https://www.gnu.org/software/ddd处找到。

有许多商业的 Python IDE,其中包括图形调试器。它们包括:

是否有工具可帮助您发现错误或执行静态分析?

Yes.

PyChecker 是一个静态分析工具,可以发现 Python 源代码中的错误并警告代码的复杂性和样式。您可以从http://pychecker.sourceforge.net/获取 PyChecker。

Pylint是另一个工具,可以检查模块是否满足编码标准,还可以编写插件来添加自定义Function。除了 PyChecker 执行的错误检查外,Pylint 还提供了一些其他Function,例如检查行长,是否根据您的编码标准正确设置了变量名,是否完全实现了语句的接口等等。 https://docs.pylint.org/提供了 Pylint Function的完整列表。

如何从 Python 脚本创建独立的二进制文件?

如果您所需的只是一个独立的程序,用户可以下载并运行该程序而无需先安装 Python 发行版,则不需要将 Python 编译为 C 代码。有许多工具可以确定程序所需的模块集,并将这些模块与 Python 二进制文件绑定在一起以生成单个可执行文件。

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

它pass递归扫描源代码(以两种形式)并在标准 Python 路径以及源代码目录(对于内置模块)中查找模块来工作。然后,它将用 Python 编写的模块的字节码转换为 C 代码(可以使用 marshal 模块转换为代码对象的数组初始化器),并创建一个定制的配置文件,该文件仅包含那些在程序。然后,它会编译生成的 C 代码,并将其与 Python 解释器的其余部分链接起来,以形成一个与脚本完全一样的自包含二进制文件。

显然,冻结需要 C 编译器。还有其他一些 Util 则没有。其中之一是 Thomas Heller 的 py2exe(仅 Windows),位于

另一个工具是 Anthony Tuininga 的cx_Freeze

是否有 Python 程序的编码标准或样式指南?

是。标准库模块所需的编码样式记录为 PEP 8

我的程序太慢。如何加快速度?

一般来说,这是一个艰难的过程。有很多技巧可以加快 Python 代码的速度。考虑将 C 语言中的部分重写作为最后的选择。

在某些情况下,可以自动将 Python 转换为 C 或 x86 汇编语言,这意味着您无需修改代码即可提高速度。

Pyrex可以将经过稍微修改的 Python 代码版本编译为 C 扩展,并且可以在许多不同的平台上使用。

Psyco是即时编译器,可将 Python 代码转换为 x86 汇编语言。如果可以使用它,Psyco 可以为关键Function提供显着的加速。

该答案的其余部分将讨论各种技巧,以加快 Python 代码的速度。除非您知道自己需要优化技巧,否则不要应用任何优化技巧,因为在分析表明某个特定Function是代码中执行频率很高的热点之后。优化几乎总是使代码不那么清晰,并且除非值得在性能上获得好处,否则您不应该为降低清晰度而付出的代价(增加开发时间,增加错误的可能性)。

Wiki 上有一个专门讨论performance tips的页面。

Guido van Rossum 在https://www.python.org/doc/essays/list2str编写了与优化有关的轶事。

需要注意的一件事是函数和(尤其是)方法调用的开销很大。如果您设计了一个纯粹的 OO 接口,其中包含许多微型函数,它们除了获得或设置实例变量或调用其他方法没有做太多的事情,您可能会考虑使用更直接的方式,例如直接访问实例变量。另请参见标准模块profile,该模块使您可以找出程序大部分时间都花在哪里(如果您有耐心,那么配置文件本身会使程序的运行速度降低一个数量级)。

请记住,您可能会从其他编程经验中了解到许多标准的优化试探法,它们很可能适用于 Python。例如,为了减少内核系统调用的开销,使用较大的写入而不是较小的写入将输出发送到输出设备可能会更快。因此,以“一发”方式写所有输出的 CGI 脚本可能比写很多小输出的脚本要快。

另外,请确保在适当的地方使用 Python 的核心Function。例如,切片允许程序使用高度优化的 C 实现在解释器的主循环的单个刻度中切成列表和其他序列对象。因此获得与以下相同的效果:

L2 = []
for i in range(3):
    L2.append(L1[i])

它更短,使用更快

L2 = list(L1[:3])  # "list" is redundant if L1 is a list.

请注意,面向Function的内置函数(例如map()zip()和 friends)可以是执行单个任务的循环的便捷加速器。例如,将两个列表的元素配对在一起:

>>> zip([1, 2, 3], [4, 5, 6])
[(1, 4), (2, 5), (3, 6)]

或计算多个正弦值:

>>> map(math.sin, (1, 2, 3, 4))
[0.841470984808, 0.909297426826, 0.14112000806, -0.756802495308]

在这种情况下,操作会很快完成。

其他示例包括join()split() 字符串对象的方法。例如,如果 s1..s7 是大(10K)字符串,则"".join([s1,s2,s3,s4,s5,s6,s7])可能比明显的s1+s2+s3+s4+s5+s6+s7快得多,因为“求和”将计算许多子表达式,而join()一次完成所有复制。要处理字符串,请使用replace()format() 字符串对象的方法。仅在不处理常量字符串模式时才使用正则表达式。您仍然可以使用旧的%操作 string % tuplestring % dictionary

请务必使用list.sort()内置方法进行排序,并请参阅sorting mini-HOWTO以获取适度高级用法的示例。 list.sort()击败了除最极端情况以外的所有其他排序技术。

另一个常见的技巧是“将循环推入函数或方法”。例如,假设您有一个运行缓慢的程序,并使用分析器确定 Python 函数ff()被多次调用。如果您注意到ff()

def ff(x):
    ... # do something with x computing result...
    return result

往往在如下循环中被调用:

list = map(ff, oldlist)

or:

for x in sequence:
    value = ff(x)
    ... # do something with value...

那么您通常可以pass将ff()重写为以下内容来消除函数调用的开销:

def ffseq(seq):
    resultseq = []
    for x in seq:
        ... # do something with x computing result...
        resultseq.append(result)
    return resultseq

并将两个示例重写为list = ffseq(oldlist)和:

for value in ffseq(sequence):
    ... # do something with value...

ff(x)的单次通话转换为ffseq([x])[0]几乎不会带来任何损失。当然,这种技术并不总是合适的,您还可以找到其他变体。

pass将函数或方法查找的结果显式存储到局部变量中,可以提高性能。像这样的循环:

for key in token:
    dict[key] = dict.get(key, 0) + 1

每次迭代解析dict.get。如果方法不会改变,则实现起来会稍快一些:

dict_get = dict.get  # look up the method once
for key in token:
    dict[key] = dict_get(key, 0) + 1

默认参数可以一次在编译时而不是在运行时确定值。只能对在程序执行期间不会更改的Function或对象(例如替换)执行此操作

def degree_sin(deg):
    return math.sin(deg * math.pi / 180.0)

with

def degree_sin(deg, factor=math.pi/180.0, sin=math.sin):
    return sin(deg * factor)

由于此技巧将默认参数用于不应更改的术语,因此仅在不考虑向用户提供可能令人困惑的 API 时才应使用该技巧。

Core Language

当变量具有值时,为什么会收到 UnboundLocalError?

pass在函数体中的某处添加赋值语句来修改以前的工作代码中的 UnboundLocalError 可能令人惊讶。

This code:

>>> 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 xtry打印未初始化的局部变量时,将导致错误。

在上面的示例中,您可以pass将其语句为全局变量来访问外部作用域变量:

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

为了提醒您(与类变量和实例变量的表面类似情况不同),实际上需要在外部范围内修改变量的值:

>>> print x
11

Python 中局部变量和全局变量的规则是什么?

在 Python 中,仅在函数内部引用的变量是隐式全局的。如果在函数体内任何位置为变量分配了值,除非明确语句为全局变量,否则将假定该变量为局部变量。

尽管起初有些令人惊讶,但片刻的考虑可以解释这一点。一方面,要求global分配变量可防止意外副作用。另一方面,如果所有全局引用都需要global,则您将一直使用global。您必须将对内置函数或导入模块的组件的每个引用语句为全局引用。这种混乱会破坏global语句对识别副作用的有用性。

为什么在循环中定义的具有不同值的 lambda 全部返回相同的结果?

假设您使用 for 循环来定义一些不同的 lambda(甚至是普通函数),例如:

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

这将为您提供一个包含 5 个计算x**2的 lambda 的列表。您可能希望它们在被调用时将分别返回014916。但是,当您实际try时,您会看到它们都返回16

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

发生这种情况是因为x并不是 lambda 的本地变量,而是在外部范围中定义的,并且在调用 lambda 时会访问它,而不是在定义时访问。在循环结束时,x的值为4,因此所有函数现在都返回4**2,即16。您还可以pass更改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的值将在第一个 lambda 中为0,在第二个 lambda 中为1,在第三个 lambda 中为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

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

在模块中使用导入的“最佳做法”是什么?

通常,请勿使用from modulename import *。这样做会使导入者的名称空间变得混乱,并使短毛猫更难检测未定义的名称。

在文件顶部导入模块。这样做可以使您的代码清楚地知道需要哪些其他模块,并且避免了有关模块名称是否在范围内的问题。每行使用一次导入可以轻松添加和删除模块导入,但是每行使用多次导入可以使用更少的屏幕空间。

如果您按以下 Sequences 导入模块,则是一个好习惯:

  • 标准库模块-例如sysosgetoptre

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

  • locally-developed modules

仅使用显式相对包导入。如果您正在编写package.sub.m1模块中的代码并想导入package.sub.m2,即使合法,也不要只写import m2。Rewritefrom package.sub import m2from . import m2

有时有必要将导入移至一个函数或类,以避免循环导入出现问题。戈登·麦克米兰(Gordon McMillan)说:

Note

如果两个模块都使用“ import ”导入形式,则可以使用循环导入。当第二个模块要从第一个模块中获取名称时(“来自模块导入名称”),并且导入位于顶层,它们将失败。这是因为第一个模块中的名称尚不可用,因为第一个模块正忙于导入第二个模块。

在这种情况下,如果第二个模块仅在一个Function中使用,则导入可以轻松地移至该Function中。在调用导入时,第一个模块将完成初始化,第二个模块可以进行其导入。

如果某些模块是特定于平台的,则可能有必要将导入移出顶级代码。在这种情况下,甚至不可能导入文件顶部的所有模块。在这种情况下,以相应的特定于平台的代码导入正确的模块是一个不错的选择。

仅在有必要解决诸如避免循环导入之类的问题或试图减少模块的初始化时间的情况下,才将导入移至局部范围(例如在函数定义内)。如果根据程序的执行方式许多导入是不必要的,则此技术特别有用。如果模块仅在该函数中使用过,则可能还需要将导入移动到该函数中。请注意,由于模块的一次初始化,因此第一次加载模块可能会很昂贵,但实际上多次加载模块实际上是免费的,仅花费了几次字典查找。即使模块名称超出范围,该模块也可能在sys.modules中可用。

为什么在对象之间共享默认值?

这种类型的错误通常会影响新手程序员。考虑以下Function:

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

第一次调用此函数时,mydict包含一个项目。第二次mydict包含两个项目,因为当foo()开始执行时,mydict开始时已有一个项目。

通常希望函数调用为默认值创建新对象。这不会发生。定义函数后,默认值仅创建一次。如果该对象被更改(如本示例中的字典),则对该函数的后续调用将引用此更改的对象。

根据定义,不可变对象(例如数字,字符串,Tuples 和None)是安全的,不能更改。对可变对象(如字典,列表和类实例)的更改可能导致混乱。

由于此Function,不使用可变对象作为默认值是一种良好的编程习惯。而是使用None作为默认值,并在函数内部,检查参数是否为None并创建一个新列表/字典/无论如何。例如,不要写:

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

but:

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

此Function可能很有用。当您拥有一个计算耗时的函数时,一种常见的技术是缓存该函数的每次调用的参数和结果值,如果再次请求相同的值,则返回缓存的值。这称为“Memory”,可以这样实现:

# Callers will never provide a third parameter for this function.
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

您可以使用包含字典而不是默认值的全局变量。这是一个品味问题。

如何将可选参数或关键字参数从一个函数传递给另一个函数?

使用函数的参数列表中的***指定符来收集参数;这使您可以将位置参数作为 Tuples,将关键字参数作为字典。然后,您可以在使用***调用另一个函数时传递这些参数:

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

如果不太在意您关心的 Python 版本早于 2.0,请使用apply()

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

参数和参数有什么区别?

Parameters由出现在函数定义中的名称定义,而arguments是在调用函数时实际传递给函数的值。参数定义函数可以接受的参数类型。例如,给定函数定义:

def func(foo, bar=None, **kwargs):
    pass
  • foo bar kwargs *是func的参数。但是,例如,在调用func时:
func(42, bar=314, extra=somevar)

42314somevar是自变量。

为什么更改列表“ y”也会更改列表“ x”?

如果您编写如下代码:

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

您可能想知道为什么将元素附加到y也更改了x

产生此结果的因素有两个:

  • 变量只是引用对象的名称。 y = x不会创建列表的副本-它会创建一个新变量y,该变量x引用相同的对象。这意味着只有一个对象(列表),并且xy都引用了该对象。

  • 列表是mutable,这意味着您可以更改其内容。

调用append()之后,可变对象的内容已从[]更改为[10]。由于两个变量都引用同一个对象,因此使用任何一个名称都将访问修改后的值[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

我们可以看到在这种情况下xy不再相等。这是因为整数是immutable,而当我们执行x = x + 1时,我们并没有pass增加其值来使 int 5突变。相反,我们正在创建一个新对象(int 6)并将其分配给x(即,更改x所指向的对象)。分配后,我们有两个对象(整数65)和两个引用它们的变量(x现在引用6y仍然引用5)。

一些操作(例如y.append(10)y.sort())会使对象发生突变,而表面上类似的操作(例如y = y + [10]sorted(y))会创建一个新对象。通常,在 Python 中(以及在标准库中的所有情况下),对对象进行突变的方法将返回None,以帮助避免混淆两种类型的操作。因此,如果您错误地写了y.sort()并认为它会给您y的排序后的副本,那么您将以None结尾,这很可能导致程序生成易于诊断的错误。

但是,在一类操作中,同一操作有时具有不同类型的不同行为:扩展赋值运算符。例如,+=突变列表,但不改变 Tuples 或整数(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()

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

请记住,参数是pass Python 中的赋值传递的。由于赋值只是创建对对象的引用,因此在调用方和被调用方之间的参数名称之间没有别名,因此本身也没有按引用进行调用。您可以pass多种方式实现所需的效果。

  • pass返回结果的 Tuples:
def func2(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
x, y = func2(x, y)
print x, y                 # output: new-value 100

这几乎总是最清晰的解决方案。

  • pass使用全局变量。这不是线程安全的,因此不建议这样做。

  • pass传递可变(就地可变)对象:

def func1(a):
    a[0] = 'new-value'     # 'a' references a mutable list
    a[1] = a[1] + 1        # changes a shared object

args = ['old-value', 99]
func1(args)
print args[0], args[1]     # output: new-value 100
  • pass传入会变异的字典:
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)
print args['a'], args['b']
  • 或将值 Binding 在类实例中:
class callByRef:
    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 callByRef
    args.b = args.b + 1         # change object in-place

args = callByRef(a='old-value', b=99)
func4(args)
print args.a, args.b

几乎没有充分的理由使这一过程变得复杂。

最好的选择是返回一个包含多个结果的 Tuples。

如何在 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

可调用对象方法的缺点是速度较慢,并且导致代码稍长。但是,请注意,可调用集合可以pass继承共享其签名:

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 中复制对象?

通常,对于一般情况,请trycopy.copy()copy.deepcopy()。并非所有对象都可以复制,但大多数可以复制。

一些对象可以更容易地复制。字典具有copy()方法:

newdict = olddict.copy()

可以pass切片复制序列:

new_l = l[:]

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

对于用户定义类的实例 x,dir(x)返回按字母 Sequences 排列的名称列表,其中包含实例属性,方法和由其类定义的属性。

我的代码如何发现对象的名称?

一般来说,它不能,因为对象实际上没有名称。从本质上讲,赋值始终将名称绑定到值。 defclass语句也是如此,但是在这种情况下,该值是可调用的。考虑以下代码:

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

可以说该类有一个名称:即使它绑定到两个名称并pass名称 B 调用,创建的实例仍被报告为类 A 的实例。但是,无法确定该实例的名称是 a 还是 b,因为两个名称都绑定到相同的值。

一般来说,您的代码不必“知道特定值的名称”。除非您故意编写内省程序,否则通常表明更改方法可能是有益的。

在 comp.lang.python 中,Fredrik Lundh 曾经很好地比喻了这个问题:

Note

获得在门廊上发现的那只猫的名字的方法相同:猫(对象)本身无法告诉您它的名字,并且它并不在乎–因此,找出它的名字的唯一方法是询问您所有的邻居(命名空间),如果它们是他们的猫(对象)…

…。如果您发现它有很多名字,甚至根本没有名字,请不要感到惊讶!

逗号运算符的优先级如何?

逗号不是 Python 中的运算符。考虑以下会话:

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

由于逗号不是运算符,而是表达式之间的分隔符,因此就像您 Importing 时一样对上述内容进行求值:

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

not:

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

各种赋值运算符(=+=等)也是如此。它们不是 true 的运算符,而是赋值语句中的句法分隔符。

C 是否等效于 C 的“?:”三元运算符?

是的,此Function是在 Python 2.5 中添加的。语法如下:

[on_true] if [expression] else [on_false]

x, y = 50, 25

small = x if x < y else y

对于 2.5 之前的版本,答案为“否”。

是否可以用 Python 编写混淆的单行代码?

是。通常,这是pass在lambda内嵌套lambda来完成的。由于 Ulf Bartelt,请参见以下三个示例:

# Primes < 1000
print 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 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

children,不要在家try这个!

数字和字符串

如何指定十六进制和八进制整数?

要指定一个八进制数字,请在八进制值之前加一个零,然后是一个小写或大写的“ o”。例如,要将变量“ a”设置为八进制值“ 10”(十进制为 8),请 Importing:

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

十六进制也一样容易。只需在十六进制数之前加上零,然后是小写或大写的“ x”。十六进制数字可以小写或大写指定。例如,在 Python 解释器中:

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

为什么-22 // 10 返回-3?

这主要是由于对i % jj具有相同符号的渴望所驱动。如果您想要这样,并且还想要:

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

然后整数除法必须返回底数。 C 还需要保留该标识,然后截断i // j的编译器需要使i % j具有与i相同的符号。

j为负数时,i % j的实际用例很少。当j为正时,有很多,实际上在所有这些中i % j都为>= 0更为有用。如果时钟现在是 10,那么 200 小时前是什么? -190 % 12 == 2是有用的; -190 % 12 == -10是一个 await 咬的虫子。

Note

在 Python 2 上,如果__future__.division无效,则a / b返回与a // b相同的结果。这也称为“经典”划分。

如何将字符串转换为数字?

对于整数,请使用内置的int()类型构造函数,例如int('144') == 144。同样,float()转换为浮点数,例如float('144') == 144.0

默认情况下,它们将数字解释为十进制,因此int('0144') == 144int('0x144')会提高ValueErrorint(string, base)将要转换的基数作为第二个可选参数,因此int('0x144', 16) == 324。如果将基数指定为 0,则使用 Python 的规则解释数字:前导“ 0”表示八进制,而“ 0x”表示十六进制数。

如果您只需要将字符串转换为数字,则不要使用内置函数eval()eval()的运行速度将大大降低,并且存在安全风险:有人可以向您传递一个 Python 表达式,该表达式可能会带来不良后果。例如,某人可以传递__import__('os').system("rm -rf $HOME"),这会删除您的主目录。

eval()还具有将数字解释为 Python 表达式的作用,例如eval('09')给出语法错误,因为 Python 将以“ 0”开头的数字视为八进制(以 8 为底)。

如何将数字转换为字符串?

若要将数字 144 转换为字符串``144'',请使用内置类型构造函数str()。如果需要十六进制或八进制表示,请使用内置函数hex()oct()。有关花式格式的信息,请参见格式字符串语法部分,例如"{:04d}".format(144)产生'0144'"{:.3f}".format(1.0/3.0)产生'0.333'。在 Python 2 中,如果参数为 int 或 long,则除法(/)运算符将返回除法 math 结果的下限,但如果参数为浮点型或复数,则除法运算符将返回除法结果的合理近似值:

>>> print('{:.3f}'.format(1/3))
0.000
>>> print('{:.3f}'.format(1.0/3))
0.333

在 Python 3 中,除法运算符的默认行为(请参见 PEP 238)已更改,但是如果从future导入division,则在 Python 2 中可以具有相同的行为:

>>> from __future__ import division
>>> print('{:.3f}'.format(1/3))
0.333

您也可以在字符串上使用operator。有关详细信息,请参见库参考手册。

如何在适当位置修改字符串?

您不能,因为字符串是不可变的。如果您需要具有此Function的对象,请try将字符串转换为列表或使用数组模块:

>>> import io
>>> s = "Hello, world"
>>> a = list(s)
>>> print a
['H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd']
>>> a[7:] = list("there!")
>>> ''.join(a)
'Hello, there!'

>>> import array
>>> a = array.array('c', s)
>>> print a
array('c', 'Hello, world')
>>> a[0] = 'y'; print a
array('c', 'yello, world')
>>> a.tostring()
'yello, world'

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

有各种各样的技术。

  • 最好是使用将字符串 Map 到函数的字典。该技术的主要优点是字符串不需要与函数名称匹配。这也是用于模拟案例构造的主要技术:
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
import foo
getattr(foo, 'bar')()

请注意,getattr()可在任何对象上使用,包括类,类实例,模块等。

在标准库中的多个地方都可以使用它,如下所示:

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

    def do_bar(self):
        ...

f = getattr(foo_instance, 'do_' + opname)
f()
def myFunc():
    print "hello"

fname = "myFunc"

f = locals()[fname]
f()

f = eval(fname)
f()

注意:使用eval()既缓慢又危险。如果您对字符串的内容没有绝对的控制权,那么有人可能会传递一个导致执行任意函数的字符串。

是否具有与 Perl 的 chomp()等效的Function,可从字符串中删除尾随的换行符?

从 Python 2.2 开始,您可以使用S.rstrip("\r\n")从字符串S的末尾删除所有出现的行终止符,而无需删除其他结尾的空格。如果字符串S代表多行,并且末尾有几行空行,则将删除所有空白行的行终止符:

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

由于通常仅一次读取一行文本时才需要这样做,因此使用S.rstrip()可以很好地工作。

对于旧版本的 Python,有两个部分替代:

  • 如果要删除所有结尾的空格,请使用字符串对象的rstrip()方法。这将删除所有结尾的空格,而不仅仅是单个换行符。

  • 否则,如果字符串S中只有一行,请使用S.splitlines()[0]

是否有等效的 scanf()或 sscanf()?

不是这样的。

对于简单的 Importing 解析,最简单的方法通常是使用字符串对象的split()方法将行拆分为空格分隔的单词,然后使用int()float()将十进制字符串转换为数字值。 split()支持可选的“ sep”参数,如果该行使用空格以外的其他内容作为分隔符,则该参数很有用。

对于更复杂的 Importing 解析,正则表达式比 C 的sscanf()更强大,并且更适合任务。

“ UnicodeError:ASCII [解码,编码]错误:序数不在范围内(128)”是什么意思?

此错误表明您的 Python 安装只能处理 7 位 ASCII 字符串。有两种方法可以解决或解决此问题。

如果您的程序必须以任意字符集编码处理数据,则应用程序运行所在的环境通常将标识它正在处理的数据的编码。您需要使用该编码将 Importing 转换为 Unicode 数据。例如,处理电子邮件或 WebImporting 的程序通常会在 Content-TypeHeaders 中找到字符集编码信息。然后可以使用它来将 Importing 数据正确转换为 Unicode。假设value所指的字符串编码为 UTF-8:

value = unicode(value, "utf-8")

将返回一个 Unicode 对象。如果数据未正确编码为 UTF-8,则上述调用将引发UnicodeError异常。

如果您只希望将具有非 ASCII 数据的字符串转换为 Unicode,则可以try首先使用 ASCII 编码进行转换,然后在失败的情况下生成 Unicode 对象:

try:
    x = unicode(value, "ascii")
except UnicodeError:
    value = unicode(value, "utf-8")
else:
    # value was valid ASCII data
    pass

可以在名为sitecustomize.py的文件中设置默认编码,该文件是 Python 库的一部分。但是,不建议这样做,因为更改 Python 范围内的默认编码可能会导致第三方扩展模块失败。

请注意,在 Windows 上,有一种称为“ mbcs”的编码,它使用特定于您当前语言环境的编码。在许多情况下,尤其是在使用 COM 时,这可能是要使用的适当默认编码。

Sequences (Tuples/Lists)

如何在 Tuples 和列表之间转换?

类型构造器tuple(seq)将任何序列(实际上是任何可迭代的)转换为具有相同 Sequences 的相同项的 Tuples。

例如,tuple([1, 2, 3])产生(1, 2, 3),而tuple('abc')产生('a', 'b', 'c')。如果参数是 Tuples,则它不进行复制,但返回相同的对象,因此在不确定某个对象已是 Tuples 时,调用tuple()很便宜。

类型构造器list(seq)将任何序列或可迭代的对象转换为具有相同 Sequences 的相同项的列表。例如,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]是除最后一个字符以外的所有字符串,这对于从字符串中删除结尾的换行符很有用。

如何以相反的 Sequences 迭代序列?

使用reversed()内置函数,这是 Python 2.4 中的新增Function:

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

这不会影响您的原始序列,但是会以相反的 Sequences 构建新副本以进行迭代。

在 Python 2.3 中,您可以使用扩展的切片语法:

for x in sequence[::-1]:
    ...  # do something with x ...

如何从列表中删除重复项?

有关执行此操作的多种方法的详细讨论,请参见 Python Cookbook:

如果您不介意对列表进行重新排序,请对其进行排序,然后从列表末尾进行扫描,并在删除过程中删除重复项:

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]

如果列表中的所有元素都可以用作字典键(即它们都是可哈希的),则通常更快

d = {}
for x in mylist:
    d[x] = 1
mylist = list(d.keys())

在 Python 2.5 和更高版本中,可以改为以下操作:

mylist = list(set(mylist))

这会将列表转换为一组,从而删除重复项,然后返回列表。

如何使用 Python 创建数组?

使用 Lists:

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

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

array模块还提供了用于创建具有紧凑表示形式的固定类型数组的方法,但是它们比列表慢索引。还要注意,数字 extensions 和其他 extensions 也定义了具有各种 Feature 的类似数组的结构。

要获取 Lisp 样式的链表,您可以使用 Tuples 模拟 con 单元格:

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

如果需要可变性,则可以使用列表而不是 Tuples。这里 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 的相同列表的引用。对一行的更改将显示在所有行中,几乎可以肯定这不是您想要的。

建议的方法是先创建所需长度的列表,然后用新创建的列表填充每个元素:

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]

更一般而言,您可以try以下Function:

def method_map(objects, method, arguments):
    """method_map([a,b], "meth", (1,2)) gives [a.meth(1,2), b.meth(1,2)]"""
    nobjects = len(objects)
    methods = map(getattr, objects, [method]*nobjects)
    return map(apply, methods, [arguments]*nobjects)

加法有效时,为什么 a_tuple [i] = ['item']引发异常?

这是由于以下事实的组合:增强的赋值运算符是* assignment *运算符,以及 Python 中可变对象和不可变对象之间的区别。

当将扩展赋值运算符应用于指向可变对象的 Tuples 的元素时,通常会进行此讨论,但是我们将使用list+=作为示例。

如果您写:

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

应该立即清除发生异常的原因:1被添加到对象a_tuple[0]指向(1),生成结果对象2,但是当我们try将计算结果2分配给 Tuples 的元素0时,由于无法更改 Tuples 元素指向的内容,因此发生错误。

在幕后,这个扩充后的赋值语句的作用大致是这样的:

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

由于 Tuples 是不可变的,因此是产生错误的操作的分配部分。

当您编写类似的内容时:

>>> 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__ magic 方法,则在执行+=增强赋值时会调用该对象,并且其返回值就是该赋值语句中使用的值; (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。赋值的finally结果是空操作,因为它是指向a_list先前指向的同Pair象的指针,但是赋值仍然发生。

因此,在我们的 Tuples 示例中,发生的情况等同于:

>>> 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]已经指向的同Pair象,但finally分配仍然会导致错误,因为 Tuples 是不可变的。

Dictionaries

如何获取字典以一致的 Sequences 显示其键?

你不能字典以不可预测的 Sequences 存储其键,因此字典元素的显示 Sequences 也将不可预测。

如果要将可打印版本保存到文件中,进行一些更改,然后将其与其他已打印词典进行比较,这可能会令人沮丧。在这种情况下,请使用pprint模块漂亮地打印字典;这些项目将按键 Sequences 显示。

一个更复杂的解决方案是将dict子类化以创建SortedDict类,该类以可预测的 Sequences 进行打印。这是此类的一个简单实现:

class SortedDict(dict):
    def __repr__(self):
        keys = sorted(self.keys())
        result = ("{!r}: {!r}".format(k, self[k]) for k in keys)
        return "{{{}}}".format(", ".join(result))

    __str__ = __repr__

尽管这并不是一个完美的解决方案,但它可以解决您可能遇到的许多常见情况。最大的缺陷是,如果字典中的某些值也是字典,则它们的值将不会以任何特定 Sequences 显示。

我想做一个复杂的事情:您可以用 Python 做 Schwartzian 转换吗?

该技术归功于 Perl 社区的 Randal Schwartz,它pass一个度量将列表中的元素排序,该度量将每个元素 Map 到其“排序值”。在 Python 中,将key参数用于sort()函数:

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

如何按另一个列表中的值对一个列表排序?

将它们合并为一个 Tuples 列表,对结果列表进行排序,然后选择所需的元素。

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

最后一步的替代方法是:

>>> result = []
>>> for p in pairs: result.append(p[1])

如果您觉得这更容易理解,则可能更愿意使用它而不是finally的列表理解方法。但是,对于长列表来说,它几乎慢了一倍。为什么?首先,append()操作必须重新分配内存,尽管它使用一些技巧来避免每次都这样做,但仍然偶尔需要这样做,这会花费很多。第二,表达式“ result.append”需要额外的属性查找,第三,必须进行所有这些函数调用会降低速度。

Objects

什么是类?

类是pass执行类语句创建的特定对象类型。类对象用作创建实例对象的模板,这些实例对象既包含数据(属性)又包含特定于数据类型的代码(方法)。

一个类可以基于一个或多个其他类,称为其 Base Class。然后,它继承其 Base Class 的属性和方法。这允许pass继承来逐步完善对象模型。您可能具有一个通用的Mailbox类,该类为邮箱提供基本的访问器方法,以及子类,例如MboxMailboxMaildirMailboxOutlookMailbox,它们处理各种特定的邮箱格式。

什么是方法?

方法是某些对象x上的函数,通常将其称为x.name(arguments...)。方法定义为类定义中的函数:

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

什么是自我?

自我只是方法的第一个参数的常规名称。对于发生定义的类的某些实例x,应将定义为meth(self, a, b, c)的方法称为x.meth(a, b, c)。被调用的方法会认为它被称为meth(x, a, b, c)

另请参见为什么必须在方法定义和调用中明确使用“自我”?

如何检查对象是给定类的实例还是子类的实例?

使用内置Functionisinstance(obj, cls)。您可以pass提供一个 Tuples 而不是单个类来检查一个对象是否是多个类中任何一个的实例。 isinstance(obj, (class1, class2, ...)),还可以检查对象是否为 Python 的内置类型之一,例如isinstance(obj, str)isinstance(obj, (int, long, float, complex))

请注意,大多数程序很少在用户定义的类上使用isinstance()。如果您自己开发类,则一种更合适的面向对象样式是在类上定义封装特定行为的方法,而不是检查对象的类并根据其是什么来做不同的事情。例如,如果您有执行某项Function的函数:

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对象。委托是pass__getattr__方法完成的;有关控制属性访问的更多信息,请咨询语言参考

请注意,对于更一般的情况,委派可能会变得更加棘手。当必须设置和检索属性时,该类也必须定义一个setattr()方法,并且必须谨慎地进行操作。 setattr()的基本实现大致等同于以下内容:

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

大多数setattr()实现必须修改self.__dict__才能为自己存储本地状态,而不会引起无限递归。

如何从覆盖它的派生类中调用 Base Class 中定义的方法?

如果您使用的是新式类,请使用内置的super()函数:

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

如果您使用的是经典类:对于诸如class Derived(Base): ...的类定义,您可以将Base(或Base的 Base Class 之一)中定义的方法meth()调用为Base.meth(self, arguments...)Base.meth是未绑定的方法,因此您需要提供self参数。

如何组织代码以更轻松地更改 Base Class?

您可以为 Base Class 定义一个别名,在类定义之前为其分配实际的 Base Class,并在整个类中使用别名。然后,您只需更改分配给别名的值即可。顺便说一句,如果您想动态地决定(例如,取决于资源的可用性)要使用哪个 Base Class,则此技巧也很方便。例:

BaseAlias = <real base class>

class Derived(BaseAlias):
    def meth(self):
        BaseAlias.meth(self)
        ...

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

Python 支持静态数据和静态方法(在 C 或 Java 方面)。

对于静态数据,只需定义一个 class 属性。要将新值分配给属性,必须在分配中明确使用类名:

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

c.count也指任何cC.count,以使isinstance(c, C)成立,除非被c本身或从c.__class__返回到C的 Base Class 搜索路径中的某个类覆盖。

注意:在 C 方法中,像self.count = 42这样的赋值会在self自己的字典中创建一个新的且不相关的实例,名为“ count”。无论是在方法内部还是在方法内部,重新绑定类静态数据名称都必须始终指定类:

C.count = 314

从 Python 2.2 开始,可以使用静态方法:

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

使用 Python 2.4 的装饰器,也可以写成

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

但是,要获得静态方法的效果,一种更直接的方法是pass一个简单的模块级函数:

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

这并不完全等效,但是在实践中足够接近。

您还可以try使用可变长度的参数列表,例如

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

相同的方法适用于所有方法定义。

我try使用__spam,但收到有关_SomeClassName__spam 的错误。

带有双前划线的双下划线变量名被“混合”,以提供一种简单而有效的方式来定义类私有变量。格式为__spam的任何标识符(至少两个前导下划线,至多一个尾随下划线)在文本上均被_classname__spam替换,其中classname是当前的类名,其中不包括前导下划线。

这不能保证隐私:外部用户仍然可以有意访问“ _classname__spam”属性,并且私有值在对象的__dict__中可见。许多 Python 程序员根本不会费心使用私有变量名称。

我的类定义了__del__,但删除对象时未调用它。

可能有多种原因。

del 语句不一定调用del(),它只是减少对象的引用计数,如果达到零,则调用del()

如果您的数据结构包含循环链接(例如,一棵树,其中每个子代都有一个父代引用,每个父代都有一个子代列表),则引用计数将永远不会回到零。 Python 偶尔会运行一种算法来检测此类循环,但是垃圾收集器可能在对数据结构的最后一次引用消失之后运行了一段时间,因此您的del()方法可能会在不方便且随机的时间被调用。如果您要重现问题,这将很不方便。更糟糕的是,对象的del()方法的执行 Sequences 是任意的。您可以运行gc.collect()强制进行收集,但是在某些病理情况下,永远不会收集对象。

尽管有循环收集器,但还是要在要使用它们的对象上定义一个明确的close()方法,这是一个好主意。 close()方法然后可以删除引用 subobjecs 的属性。不要直接调用del()del()应该调用close(),而close()应该确保同Pair象可以被多次调用。

避免循环引用的另一种方法是使用weakref模块,该模块允许您指向对象而无需增加其引用计数。例如,树数据结构应使用弱引用作为其父引用和兄弟引用(如果需要它们!)。

如果对象曾经是函数中的一个局部变量,该函数在 except 子句中捕获了一个表达式,则可能是对该对象的引用仍然存在于该函数的堆栈框架中,如堆栈跟踪中所包含的那样。通常,调用sys.exc_clear()将清除最后记录的异常来解决此问题。

最后,如果您的del()方法引发异常,则会向sys.stderr打印一条警告消息。

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

Python 不会跟踪类(或内置类型)的所有实例。您可以对类的构造函数进行编程,以pass保留对每个实例的弱引用列表来跟踪所有实例。

为什么 id()的结果似乎不唯一?

内置id()返回一个整数,该整数在对象的生存期内保证是唯一的。由于在 CPython 中,这是对象的内存地址,因此经常发生以下情况:从内存中删除对象后,下一个新创建的对象将分配到内存中的同一位置。这个例子说明了这一点:

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

这两个 ID 属于不同的整数对象,它们是在执行id()调用之后立即创建并删除的。为确保您要检查其 ID 的对象仍然存在,请创建对该对象的另一个引用:

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

Modules

如何创建.pyc 文件?

首次导入模块时(或源比当前编译文件更新的时候),应在与.py文件相同的目录中创建包含编译代码的.pyc文件。

无法创建.pyc文件的原因之一是目录的权限问题。例如,如果您以一个用户身份开发但又以另一个用户身份运行(例如,您正在使用 Web 服务器进行测试),则会发生这种情况。如果要导入模块,则.pyc 文件的创建是自动的,并且 Python 有能力(权限,可用空间等)将已编译的模块写回到目录中。

在顶级脚本上运行 Python 不被视为导入,也不会创建.pyc。例如,如果您有一个顶级模块foo.py导入了另一个模块xyz.py,则在您运行foo时,将导入xyz.pyc,因此将创建xyz.pyc,但是由于未导入foo.py不会创建foo.pyc文件。

如果需要创建foo.pyc(即为未导入的模块创建.pyc文件),则可以使用py_compilecompileall模块。

py_compile模块可以手动编译任何模块。一种方法是在该模块中交互使用compile()函数:

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

这会将.pycfoo.py写入相同的位置(或者您可以使用可选参数cfile覆盖它)。

您还可以使用compileall模块自动编译一个或多个目录中的所有文件。您可以pass运行compileall.py并提供包含要编译的 Python 文件的目录的路径,从 shell 提示符下执行此操作:

python -m compileall .

如何找到当前的模块名称?

模块可以pass查看 sched 义的全局变量__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

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

  • 主要 import 商品 foo

  • 为 foo 创建空的全局变量

  • foo 被编译并开始执行

  • fooimport 栏

  • 栏的空全局被创建

  • 条被编译并开始执行

  • bar 导入 foo(因为已经有一个名为 foo 的模块,所以它是空操作)

  • bar.foo_var = foo.foo_var

最后一步失败了,因为 Python 尚未完成对foo的解释,并且foo的全局符号字典仍然为空。

当您使用import foo,然后try在全局代码中访问foo.foo_var时,也会发生同样的事情。

此问题(至少)有三种可能的解决方法。

Guido van Rossum 建议避免使用from <module> import ...,而应将所有代码放在函数中。全局变量和类变量的初始化应仅使用常量或内置函数。这意味着来自导入模块的所有内容都被称为<module>.<name>

Jim Roskind 建议在每个模块中按以下 Sequences 执行步骤:

  • 导出(不需要导入 Base Class 的全局变量,函数和类)

  • import条语句

  • 活动代码(包括从导入值初始化的全局变量)。

范·罗瑟姆(van Rossum)不太喜欢这种方法,因为 import 货出现在一个陌生的地方,但是确实可行。

Matthias Urlichs 建议重组代码,这样一来就不必进行递归导入。

这些解决方案不是互斥的。

__import __('x.y.z')返回<module 'x'>;我怎么得到 z?

考虑改用importlib中的便捷Functionimport_module()

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

当我编辑导入的模块并重新导入时,更改不会显示。为什么会这样?

出于效率和一致性的考虑,Python 仅在首次导入模块时才读取模块文件。如果没有,则在一个由许多模块组成的程序中,每个模块都导入相同的基本模块,该基本模块将被解析并重新解析多次。要强制重新读取已更改的模块,请执行以下操作:

import modname
reload(modname)

警告:此技术并非 100%防呆。特别是,包含如下语句的模块

from modname import some_objects

将 continue 使用旧版本的导入对象。如果模块包含类定义,则现有的类实例将不会更新为使用新的类定义。这可能导致以下自相矛盾的行为:

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

如果您打印出类对象,则表明问题的本质:

>>> c.__class__
<class cls.C at 0x7352a0>
>>> cls.C
<class cls.C at 0x4198d0>