Programming FAQ
Contents
General Questions
是否有带有断点,单步执行等Function的源代码级调试器?
Yes.
下面介绍了几种用于 Python 的调试器,并且内置函数breakpoint()允许您使用其中的任何一个。
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,其中包括图形调试器。它们包括:
-
机翼 IDE(https://wingware.com/)
-
Komodo IDE(https://komodoide.com/)
-
PyCharm (https://www.jetbrains.com/pycharm/)
是否有工具可帮助您发现错误或执行静态分析?
Yes.
PyChecker 是一个静态分析工具,可以发现 Python 源代码中的错误并警告代码的复杂性和样式。您可以从http://pychecker.sourceforge.net/获取 PyChecker。
Pylint是另一个工具,可以检查模块是否满足编码标准,还可以编写插件来添加自定义Function。除了 PyChecker 执行的错误检查外,Pylint 还提供了一些其他Function,例如检查行长,是否根据您的编码标准正确设置了变量名,是否完全实现了语句的接口等等。 https://docs.pylint.org/提供了 Pylint Function的完整列表。
诸如Mypy,Pyre和Pytype之类的静态类型检查器可以检查 Python 源代码中的类型提示。
如何从 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。
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(x)
try打印未初始化的局部变量时,将导致错误。
在上面的示例中,您可以pass将其语句为全局变量来访问外部作用域变量:
>>> 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 个计算x**2
的 lambda 的列表。您可能希望它们在被调用时将分别返回0
,1
,4
,9
和16
。但是,当您实际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)
在模块中使用导入的“最佳做法”是什么?
通常,请勿使用from modulename import *
。这样做会使导入者的名称空间变得混乱,并使短毛猫更难检测未定义的名称。
在文件顶部导入模块。这样做可以使您的代码清楚地知道需要哪些其他模块,并且避免了有关模块名称是否在范围内的问题。每行使用一次导入可以轻松添加和删除模块导入,但是每行使用多次导入可以使用更少的屏幕空间。
如果您按以下 Sequences 导入模块,则是一个好习惯:
-
标准库模块-例如
sys
,os
,getopt
,re
-
第三方库模块(安装在 Python 的 site-packages 目录中的所有模块)–例如 mx.DateTime,ZODB,PIL.Image 等
-
locally-developed modules
有时有必要将导入移至一个函数或类,以避免循环导入出现问题。戈登·麦克米兰(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 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
如何将可选参数或关键字参数从一个函数传递给另一个函数?
使用函数的参数列表中的*
和**
指定符来收集参数;这使您可以将位置参数作为 Tuples,将关键字参数作为字典。然后,您可以在使用*
和**
调用另一个函数时传递这些参数:
def f(x, *args, **kwargs):
...
kwargs['width'] = '14.3c'
...
g(x, *args, **kwargs)
参数和参数有什么区别?
Parameters由出现在函数定义中的名称定义,而arguments是在调用函数时实际传递给函数的值。参数定义函数可以接受的参数类型。例如,给定函数定义:
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
。
产生此结果的因素有两个:
-
变量只是引用对象的名称。
y = x
不会创建列表的副本-它会创建一个新变量y
,该变量x
引用相同的对象。这意味着只有一个对象(列表),并且x
和y
都引用了该对象。 -
列表是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
我们可以看到在这种情况下x
和y
不再相等。这是因为整数是immutable,而当我们执行x = x + 1
时,我们并没有pass增加其值来使 int 5
突变。相反,我们正在创建一个新对象(int 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
结尾,这很可能导致程序生成易于诊断的错误。
但是,在一类操作中,同一操作有时具有不同类型的不同行为:扩展赋值运算符。例如,+=
突变列表,但不改变 Tuples 或整数(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()。
如何编写带有输出参数的函数(按引用调用)?
请记住,参数是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 排列的名称列表,其中包含实例属性,方法和由其类定义的属性。
我的代码如何发现对象的名称?
一般来说,它不能,因为对象实际上没有名称。从本质上讲,赋值始终将名称绑定到值。 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>
可以说该类有一个名称:即使它绑定到两个名称并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 的“?:”三元运算符?
就在这里。语法如下:
[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 编写混淆的单行代码?
是。通常,这是pass在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
children,不要在家try这个!
函数参数列表中的斜杠(/)是什么意思?
函数的参数列表中的斜杠表示该函数之前的参数仅是位置参数。仅位置参数是没有外部可用名称的参数。调用仅接受位置参数的函数时,参数仅根据其位置 Map 到参数。例如,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),请 Importing:
>>> 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
相同的符号。
j
为负数时,i % j
的实际用例很少。当j
为正时,有很多,实际上在所有这些中i % j
都为>= 0
更为有用。如果时钟现在是 10,那么 200 小时前是什么? -190 % 12 == 2
是有用的; -190 % 12 == -10
是一个 await 咬的虫子。
如何将字符串转换为数字?
对于整数,请使用内置的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")
,这会删除您的主目录。
eval()还具有将数字解释为 Python 表达式的作用,例如eval('09')
给出语法错误,因为 Python 不允许以十进制数字开头的“ 0”(“ 0”除外)。
如何将数字转换为字符串?
若要将数字 144 转换为字符串``144'',请使用内置类型构造函数str()。如果需要十六进制或八进制表示,请使用内置函数hex()或oct()。有关花式格式化,请参见格式化的字符串 Literals和格式字符串语法部分,例如"{:04d}".format(144)
产生'0144'
和"{:.3f}".format(1.0/3.0)
产生'0.333'
。
如何在适当位置修改字符串?
您不能,因为字符串是不可变的。在大多数情况下,您应该简单地从要组装字符串的各个部分中构造一个新字符串。但是,如果您需要一个具有修改就地 unicode 数据能力的对象,请try使用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'
如何使用字符串调用函数/方法?
有各种各样的技术。
- 最好是使用将字符串 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
- 使用内置Functiongetattr():
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,可从字符串中删除尾随的换行符?
您可以使用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()?
不是这样的。
对于简单的 Importing 解析,最简单的方法通常是使用字符串对象的split()方法将行拆分为空格分隔的单词,然后使用int()或float()将十进制字符串转换为数字值。 split()
支持可选的“ sep”参数,如果该行使用空格以外的其他内容作为分隔符,则该参数很有用。
对于更复杂的 Importing 解析,正则表达式比 C 的sscanf()
更强大,并且更适合任务。
“ UnicodeDecodeError”或“ UnicodeEncodeError”错误是什么意思?
Performance
我的程序太慢。如何加快速度?
一般来说,这是一个艰难的过程。首先,这是进一步潜水之前要记住的事情 Lists:
-
性能 Feature 因 Python 实现而异。本常见问题集中于CPython。
-
行为可能会因 os 而异,尤其是在谈论 I/O 或多线程时。
-
在try优化任何代码之前,应始终在程序中找到热点(请参见profile模块)。
-
编写基准测试脚本将使您在搜索改进时可以快速迭代(请参见timeit模块)。
-
强烈建议(pass单元测试或任何其他技术)具有良好的代码覆盖率,然后再潜在地引入隐藏在复杂优化中的回归。
话虽如此,有许多技巧可以加快 Python 代码的速度。以下是一些一般性原则,它们对达到可接受的性能水平有很大帮助:
-
与try在整个代码中使用微优化技巧相比,使算法更快(或更改为更快的算法)可产生更大的yield。
-
使用正确的数据结构。研究Built-in Types和collections模块的文档。
-
当标准库提供执行某项操作的 Primitives 时,它可能(尽管不能保证)比您想出的任何替代方法都快。对于用 C 编写的基元,例如内置函数和某些扩展类型,这是双重事实。例如,请确保使用list.sort()内置方法或相关的sorted()函数进行排序(有关适当高级用法的示例,请参见排序方式)。
-
抽象往往会创建间接关系,并迫使解释器工作更多。如果间接级别超过完成的有用工作量,您的程序将变慢。您应该避免过多的抽象,尤其是在微小的函数或方法的形式下(通常也不利于可读性)。
如果您已经达到了纯 Python 所允许的极限,则可以使用一些工具将您带到更远的地方。例如,Cython可以将经过稍微修改的 Python 代码版本编译为 C 扩展,并且可以在许多不同的平台上使用。 Cython 可以利用编译(和可选的类型 Comments)优势使您的代码比解释时快得多。如果您对自己的 C 编程技能有信心,也可以编写一个 C 扩展模块自己。
See also
专门用于performance tips的 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
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]
如果列表中的所有元素都可以用作设置键(即它们均为hashable),则通常更快
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]
加法有效时,为什么 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 是不可变的。
我想做一个复杂的事情:您可以用 Python 做 Schwartzian 转换吗?
该技术归功于 Perl 社区的 Randal Schwartz,它pass一个度量将列表中的元素排序,该度量将每个元素 Map 到其“排序值”。在 Python 中,对list.sort()方法使用key
参数:
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 = sorted(pairs)
>>> pairs
[("I'm", 'else'), ('by', 'sort'), ('sorting', 'to'), ('what', 'something')]
>>> 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
类,该类为邮箱提供基本的访问器方法,以及子类,例如MboxMailbox
,MaildirMailbox
和OutlookMailbox
,它们处理各种特定的邮箱格式。
什么是方法?
方法是某些对象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, 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()Function:
class Derived(Base):
def meth(self):
super(Derived, self).meth()
对于 3.0 之前的版本,您可能正在使用经典类:对于诸如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
也指任何c
的C.count
,以使isinstance(c, C)
成立,除非被c
本身或从c.__class__
返回到C
的 Base Class 搜索路径中的某个类覆盖。
注意:在 C 方法中,像self.count = 42
这样的赋值会在self
自己的字典中创建一个新的且不相关的实例,名为“ count”。无论是在方法内部还是在方法内部,重新绑定类静态数据名称都必须始终指定类:
C.count = 314
静态方法是可能的:
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()
方法然后可以删除引用子对象的属性。不要直接调用del() – del()应该调用close()
,而close()
应该确保同Pair象可以被多次调用。
避免循环引用的另一种方法是使用weakref模块,该模块允许您指向对象而无需增加其引用计数。例如,树数据结构应使用弱引用作为其父引用和兄弟引用(如果需要它们!)。
最后,如果您的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
文件的目录的__pycache__
子目录中创建包含编译代码的.pyc
文件。 .pyc
文件的文件名以与.py
文件相同的名称开头,以.pyc
结尾,其中间部分取决于创建该文件的特定python
二进制文件。 (有关详情,请参见 PEP 3147。)
可能无法创建.pyc
文件的一个原因是包含源文件的目录存在权限问题,这意味着无法创建__pycache__
子目录。例如,如果您以一个用户身份开发但又以另一个用户身份运行(例如,您正在使用 Web 服务器进行测试),则会发生这种情况。
除非设置了 PYTHONDONTWRITEBYTECODE环境变量,否则在导入模块时将自动创建.pyc 文件,并且 Python 具有创建__pycache__
子目录并将已编译的模块写入该目录的Function(权限,可用空间等)。子目录。
在顶级脚本上运行 Python 不被视为导入,也不会创建.pyc
。例如,如果您有一个顶级模块foo.py
导入了另一个模块xyz.py
,则在运行foo
时(pass Importingpython foo.py
作为 shell 命令),将为xyz
创建.pyc
,因为已导入xyz
,但不会提供.pyc
文件由于未导入foo.py
为foo
创建。
如果需要为foo
创建.pyc
文件,即为未导入的模块创建.pyc
文件,则可以使用py_compile和compileall模块。
py_compile模块可以手动编译任何模块。一种方法是在该模块中交互使用compile()
函数:
>>> import py_compile
>>> py_compile.compile('foo.py')
这会将.pyc
写入与foo.py
相同位置的__pycache__
子目录(或者您可以使用可选参数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 importlib
import modname
importlib.reload(modname)
警告:此技术并非 100%防呆。特别是,包含如下语句的模块
from modname import some_objects
将 continue 使用旧版本的导入对象。如果模块包含类定义,则现有的类实例将不会更新为使用新的类定义。这可能导致以下自相矛盾的行为:
>>> 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
如果您打印出类对象的“标识”,那么问题的性质就很清楚了:
>>> hex(id(c.__class__))
'0x7352a0'
>>> hex(id(cls.C))
'0x4198d0'