Python中少有人回答的问题

The Python IAQ: Infrequently Answered Questions

1 Q: 什么是”少有人回答的问题(Infrequently Answered Question)” ?

一个问题之所以很少有人回答,要么是因为很少有人知道问题的答案,要么是因为它涉及到一个晦涩而隐蔽的知识点(但可能是你关心的)。我过去认为是我在Java IAQ中发明了这个词组,但是它也出现在了以数据丰富而著称的About.com Urban Legends网站上. 关于Python的FAQ有很多,但是Python的IAQ只有这一个。(“少见问题列表”倒是有一些,其中一个是有讽刺意味的C。)

2 Q: finally子句中的代码每次都会被执行,对吗?

每次?应该说,几乎每次。在try子句被执行后,无论是否出现异常,finally子句中的代码都会被执行,即使调用了sys.exit. 不过如果程序没有执行到finally子句的话,它就没有办法运行了。下面的代码中,无论choice取何值,都会发生这样的情况:

try:
if choice:
while 1:
pass
else:
print "Please pull the plug on your computer sometime soon..."
time.sleep(60 * 60 * 24 * 365 * 10000)
finally:
print "Finally ..."

3 Q: 多态真是太棒了!无论一个列表(list)中的元素是什么类型,我都可以用sort对它排序,对吗?

不对。考虑这种情况:

>>> x = [1, 1j]
>>> x.sort()
Traceback (most recent call last):
File "<pyshell#13>", line 1, in ?
x.sort()
TypeError: cannot compare complex numbers using <, <=, >, >=

(1j是一个数,表示-1的平方根)问题在于:sort方法(在目前的实现中)使用lt方法来 比较元素的大小。而lt方法拒绝比较复数的大小(因为它们是不能排序的)。奇怪的是, complex.lt会毫不犹豫的比较复数与字符串,列表(list)和其他所有类型,除了复数。所以答案是,你可以对支持lt方法的对象序列(sequence)进行排序(当然如果将来实现变了,可能就是其它方法了)。

对于问题的地一部份,“多态真棒”,我同意。但是Python有时会让使用多态变得困难,因为许多Python的类型(比如序列和数)的定义不太符合规则。

4 Q: 在Python中我能写++x和x++吗?

从语法上说,++x能, x++不能。但是从实际使用来说,别这样做。这么说什么意思?

  • 可以, ++x是合法的Python语法。不过如果你是一个C++或者Java程序员的话,它表示不是你想的那个意思。加号+是一个单目前缀操作符,所以++x被解析为+(+x),它表示的(至少对于数字来说)就是x。
  • 不可以, x++本身就不是一个合法的表达式, 虽然在某些上下文时合法。比如, x++ -y被解析为x++(-(y)), 对于数字来说,等于x - y。当然,你可以创建一个类,让++x有(很有限的)意义。比如可以让这个类保存一个数字,然后使单目操作符+使它增加0.5(或者有0.5的概率增加1,如果你喜欢随机化算法),但是…
  • 不可以,那样真傻。最好还是用Python 2.0已经中加入的x += 1。 进一步的问题:为什么Python不允许 x++? 我相信原因与Python不允许在表达式中赋值一样: Python想要清晰的区分语句和表达式。如果我觉得这两者应该有所区别,那么不允许++就是最好的决定。另一方面,函数语言的鼓吹者认为语句就应该是表达式。我跟我的丹麦老乡,Bjarne Stroustrup,都这样认为。他在The Design and Evolution of C++中说:“如果是从头来设计一种语言的话,我会按照Algol68的方式,让每条语句和声明都是一个有返回值的表达式”。

5 Q: 我能使用C++中对ostreams那样的语法吗,像这样么: count << x << y …?

当然可以。如果你不喜欢写”print x,y”,你可以试试这个:

import sys

class ostream:
def __init__(self, file):
self.file = file def __lshift__(self, obj):
self.file.write(str(obj));
return self cout = ostream(sys.stdout)
cerr = ostream(sys.stderr)
nl = '\n'
-----------------------------------
cout << x << " " << y << nl

(本文中所有的文件中的代码都在横线以上,使用这些代码的例子在横线以下。)这样你就可以使用一种不同的语法了,但是它不能给你带来一种新的输出格式,它只是把Python中以有str的格式封装了一层而已。这个做法很像Java里面的toString()格式。C++使用的是一种迥异的格式:它没有定义一组把对象转换为字符串的规则,而定义了一种把对象打印到流的规则(也许是不完整的规则,因为很多C++程序仍然使用printf)。用流来实现会更加复杂,但是它的优势在于如果你需要打印一个相当巨大的对象,就不用创建一个巨大的临时对象来做这件事。

6 Q: 如果我喜欢C++的printf呢?

在Python中定义一个printf不是一个坏主意. 你可能认为printf(“%d = %s”, num, result)比print “%d = %s” % (num, result)更加自然, 因为那一对括号在更熟悉的位置(而且你不想要那个%)。更和况, 满足这个需求轻而易举:

def printf(format, *args): print format % args,

即使是像这样的一行代码,也有几个不同实现。首先,我必需要决定是否在结尾添加逗号。为了更像C++, 我决定加上(这就意味着如果你想在结尾换行,你需要自己在格式字符串的末尾添加)。其次,结尾处会打印一个空格。如果你不想要它,使用sys.stdout.write来代替print. 最后, 把一切都变得更像C好是一件好事吗? 是,因为你需要一个打印函数(而不是一个打印语句)在只接受函数不接受语句的地方使用。比如,在lambda表达式中和map的第一个参数。事实上,这样一个函数使用起来是很趁手的,你可能想要一个没有格式化功能的:

def prin(x): print x,

现在map(prin, seq)将打印seq中的每一个元素. 但是map(print, seq)是一个语法错误. 我曾经见过有些粗心大意的程序员(好吧, 没错, 我自己就是. 但是我知道我自己很粗心 )认为把这两个函数合二为一是个好主意, 像这样:

def printf(format, *args): print str(format) % args,

这样 printf(42), printf(‘A multi-line\n message’)和 printf(‘%4.2f’, 42)都能工作。但是当你用了pring(‘100% guaranteed’)或者是其他任何含有%字符却并不是一个格式化指令时,”好主意”就会变成”我想啥呢?”。如果你真的实现了这么一个printf,它需要这样的注释:

def printf(format, *args):
  """使用第一个参数作为格式字符串来格式化args, 然后打印.
  如果format不是字符串, 将被str转换成字符串. 如果x可能含
  有%和反斜杠字符, 你必须使用printf('%s', x)来代替   printf(x).
  """
print str(format) % args,

7 Q: 关于字典(Dictionary),有没有更好的语法? 我使用的键(key)都是标识符.

有!用一对引号来包括键的确是一件麻烦的事情,尤其当键是一个很长的字符串时. 起初我认为Python中加入特别的语法是有帮助的,用{a=1, b=2}来代替现在必需的{‘a’:1, ‘b’:2}。在Python 2.3中,你可以用的语法是dict(a=1, b=2, c=3, dee=4),这和我的想法一样好。在Python 2.3以前,我使用一个只有一行的函数:

def Dict(**dict): return dict

一个读者指出,对于散列Perl也有类似的特殊符号: 在Perl中对于散列文本,你可以写(“a”, 1, “b”, 2)或者(a=>1, b=>2)。这是事实,但不是事实的全部。”man perlop”说”=>符号最多只是逗号操作符的同意词…”而且事实上当a和b是barewords时,你可以写(a, 1, b, 2)。但是,就像Dag Asheim指出的,如果你打开strict,你将会从这个写法中得到一个错误。你必须要么使用字符串,要么使用=>操作符。最后,Larry Wall已经申明,”Perl 6中将不会有bareword”。(关于perl的这以部分,我的翻译可能有很大问题,因为我根本不会Perl! –译注)

8 Q: 那么,对象有没有类似的简便办法呢?

的确是有的。如果你想要创建一个对象来把数据保存在不同的域中,下面的代码就可以做到:

class Struct:
def __init__(self, **entries): self.__dict__.update(entries)
>>> globals = Struct(answer=42, linelen = 80, font='courier')
>>> globals.answer
42
>>> globals.answer = 'plastics'
>>> vars(globals)
{'answer': 'plastics', 'font': 'courier', 'linelen': 80}

从本质上说,我们在这里做的是创建一个匿名类。好吧,我知道globals的类是 Struct,但是因为我们在它里面添加了slots,就像是创建了一个新的,未命名的类(这和lambda创建匿名函数是很像的)。我讨厌再给Struct添加什么了,因为它现在很简洁,不过如果你添加下面的方法,就可以漂亮打印出它的每个结构。

def __repr__(self):
args = ['%s=%s' % (k, repr(v)) for (k,v) in vars(self).items()]
return 'Struct(%s)' % ', '.join(args)
>>> globals
------------------------------------------------
Struct(answer='plastics', font='courier', linelen=80)

9 Q: 这样创建新对象是很方便,但是要更新时怎么办呢?

是这样的,字典是有一个update方法的,所以当d是一个字典时,你可以用d.update(dict(a=100, b=200))。但是对象没有对应的方法,所以你只能用obj.a = 100; obj.b = 200。或者你可以定义一个函数update(x, a=100, b=200)来更新x,无论它是字典还是对象都可以:

import types

def update(x, **entries):
if type(x) == types.DictType: x.update(entries)
else: x.__dict__.update(entries)
return x

把它用于构造函数特别漂亮:

def __init__(self, a, b, c, d=42, e=None, f=()):
update(self, a=a, b=b, c=c, d=d, e=e, f=f)

10 Q: 我能创建一个默认值为0或者[]的或者别的什么的字典么?

如果你常常要对某个东西计数,咱们会有同感: count[x] += 1比被迫用的count[x] = count.get(x, 0) + 1要优美许多。在Python 2.2以后,继承内建的dict类可以轻松的搞定这个。我把它叫做我的DefaultDict。注意copy.deepcopy的使用: 有了它,就不会让dict里面的每个key都使用同一个[]作为默认值(虽然拷贝0浪费了一点时间,不过如果你使用更新和访问比初始化更频繁的话,还算可以接受):

class DefaultDict(dict):
"""Dictionary with a default value for unknown keys."""
def __init__(self, default):
self.default = default def __getitem__(self, key):
if key in self: return self.get(key)
return self.setdefault(key, copy.deepcopy(self.default))
--------------------------------------
>>> d = DefaultDict(0)
>>> d['hello'] += 1
>>> d
{'hello': 1}
>>> d2 = DefaultDict([])
>>> d2[1].append('hello')
>>> d2[2].append('world')
>>> d2[1].append('there')
>>> d2
{1: ['hello', 'there'], 2: ['world']} def bigrams(words):
"Counts of word pairs, in a dict of dicts."
d = DefaultDict(DefaultDict(0))
for (w1, w2) in zip([None] + words, words + [None]):
d[w1][w2] += 1
return d >>> bigrams('i am what i am'.split())
{None: {'i': 1}, 'i': {'am': 2}, 'what': {'i': 1}, 'am': {None: 1, 'what': 1}}

值得注意的是,如果没有DefaultDict,bigram例子程序中的dw1 += 1就大概应该象这样:

d.setdefault(w1,{}).setdefault(w2, 0); d[w1][w2] += 1

11 Q: 嘿,你能用0.0007KB或者更少的代码做一个矩阵变换么?

我还以为你永远不会问呢. 如果你用序列组成的序列来表示矩阵的话,用zip就可以搞定了:

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

要想理解它,你需要知道f(*m)就像于apply(f,m)。你问的是一个古老的Lisp问题,在Python中它的等价答案是map(None, *m),但是用Chih-Chung Chang建议的zip版代码会更短小。你可能认为这些代码唯一的用处就是在Letterman的Stupid Programmer’sTricks(David Michael Letterman, 美国晚间脱口秀主持人,他主持的一个著名节目是Stupid Pet Tricks——译注)中露脸,但是有一天我遇到了这个问题:有一个数据库行的列表,每一行中都是排序过的值的列表。找出每一列中不重复的值,组成一个列表。我的答案是:

possible_values = map(unique, zip(*db))

12 Q: 用f(m)的技巧很酷. 有没有同样的语法可以用在方法调用上, 比如x.f(y)?

这个问题暴露一个错误的概念。根本就没有方法调用的语法!Python语法中,有函数调用的,也有从对象中取得域的,也有绑定方法的。把这三者结合起来,就让x.f(y)看起来像一块单独的语法,而事实上,它等价于(x.f)(y),后者又等价于(getattr(x, ‘f’))(y)。我猜你可能不相信我,来看:

class X:
def f(self, y): return 2 * y
--------------------------------------
>>> x = X()
>>> x.f
<bound method X.f of <__main__.X instance at 0x009C7DB0>>
>>> y = 21
>>> x.f(y)
42
>>> (x.f)(y)
42
>>> (getattr(x, 'f'))(y)
42
>>> xf = x.f
>>> xf(y)
42
>>> map(x.f, range(5))
[0, 2, 4, 6, 8]

所以这个问题的答案是:你可以在方法调用中使用y或*y(或者其他任何你可以放在函数调用中的),因为方法调用就是函数调用。

13 Q: 你能用用0行代码实现Python的抽象类吗? 4行呢?

Java中有一个abstract关键词。你可以用它来定义一个只能继承不能被实例化的抽象类,该类中所有的抽象方法都需要你来实现。很少有人知道在Python中,你可以用几乎一样的方式使用abstract。不同的是,当你想要调用一个没有实现的方式时,你得到的是一个运行时错误而不是编译错误。比较下面的代码:

## Python
class MyAbstractClass:
def method1(self): abstract class MyClass(MyAbstractClass):
pass
--------------------------------------
>>> MyClass().method1()
Traceback (most recent call last):
...
NameError: name 'abstract' is not defined

==============================================

    /* Java */
public abstract class MyAbstractClass {
public abstract void method1();
} class MyClass extends MyAbstractClass {}
----------------------------------------------
% javac MyAbstractClass
MyAbstractClass.java:5:
class MyClass must be declared abstract.
It does not define void method1() from class MyAbstractClass.

别花太多时间在Python语言参考手册里面寻找abstract关键词,它根本就不在那里。我把它加入了Python语言中,并且最美妙的是,它的实现用了0行代码! 当你调用methord1,你会得到一个NameError错误,因为不存在abstract变量。(你也许会说这是欺骗,如果有人定义一个变量叫做abstract它就没有效果了) 但是如果代码中依赖的一个变量被人重定义的话,任何程序都难逃错误的命运。这里唯一的区别就是我们依赖的是没有定义的变量。

如果你愿意写abstract()替代abstract,那么你可以定义一个函数抛出一个更有意义的NotImplementedError以取代NameError。(同样,如果有人重定义abstract为零参数函数以外的任何东西,你还是会得到一个错误信息。)为了让abstract的错误信息看起来舒服一点,只需去函数调用栈(stack frame)中看看谁是这个讨厌的调用者:

def abstract():
import inspect
caller = inspect.getouterframes(inspect.currentframe())[1][3]
raise NotImplementedError(caller + ' must be implemented in subclass')
----------------------------------------------
>>> MyDerivedClass().method1()
Traceback (most recent call last):
...
NotImplementedError: method1 must be implemented in subclass

14 Q: 在Python中我怎么实现枚举类型呢?

这个问题没有一个答案,因为在Python中有好几个答案,取决于你对枚举的期望。如果你只是想有几个变量,每个都有不同的整数值,你可以这样:

red, green, blue = range(3)

缺点是当你想在左边添加一个新的变量,需要同时增加右边的整数。不过这不算太坏,因为当你忘记的时候Python会抛出一个错误。如果你把枚举隔离在类中可能更干凈一点:

class Colors:
red, green, blue = range(3)

现在Colors.red会得到0, 并且dir(Colors)可能也能派上用场(虽然你还需要忽略docmodule两项). 如果你想完全控制每个枚举变量的值, 可以使用好几个问题以前的Struct函数, 就像下面:

Enum = Struct
Colors = Enum(red=0, green=100, blue=200)

尽管这些简单的办法通常已经够了,可有人还想要更多。 在python.orgASPNfaqts上都有枚举类型的实现。下面是我的版本,它(几乎)涵盖所有人的需要,并且仍然保持合理的简洁(一共44行,其中有22行代码):

class Enum:

    """创建一个可的枚举类型, 然后给他添加变量/值对. 构造函数
  和.ints(names)方法接受变量名的列表并且将连续的整数赋予他们. 方法.strs(names)将每个变量名赋给它自己(就是说变量'v'有值'v'). 方法.vals(a=99, b=200) 让你可以给任何变量赋任何值. "变量名列表"也可以是一个字符串, 它将被.split()分开. 方法.end()返回最大整数值加1,比如: opcode = Enum("add sub load store").vals(illegal=255).""" def __init__(self, names=[]): self.ints(names) def set(self, var, val):
"""Set var to the value val in the enum."""
if var in vars(self).keys(): raise AttributeError("duplicate var in enum")
if val in vars(self).values(): raise ValueError("duplicate value in enum")
vars(self)[var] = val
return self def strs(self, names):
"""Set each of the names to itself (as a string) in the enum."""
for var in self._parse(names): self.set(var, var)
return self def ints(self, names):
"""Set each of the names to the next highest int in the enum."""
for var in self._parse(names): self.set(var, self.end())
return self def vals(self, **entries):
"""Set each of var=val pairs in the enum."""
for (var, val) in entries.items(): self.set(var, val)
return self def end(self):
"""One more than the largest int value in the enum, or 0 if none."""
try: return max([x for x in vars(self).values() if type(x)==type(0)]) + 1
except ValueError: return 0 def _parse(self, names):
### If names is a string, parse it as a list of names.
if type(names) == type(""): return names.split()
else: return names

下面是使用它的例子:

>>> opcodes = Enum("add sub load store").vals(illegal=255)
>>> opcodes.add
0
>>> opcodes.illegal
255
>>> opcodes.end()
256
>>> dir(opcodes)
['add', 'illegal', 'load', 'store', 'sub']
>>> vars(opcodes)
{'store': 3, 'sub': 1, 'add': 0, 'illegal': 255, 'load': 2}
>>> vars(opcodes).values()
[3, 1, 0, 255, 2]

注意这些方法都是级联(cascaded)的,在构造函数后你可以把.strs, .ints和.vals组合在一行代码中。还要注意的dir和vals辅助使用,它们不会被任何东西干扰, 除了你定义的变量。为了遍历所有的枚举值,你可以使用for x in vars(opcodes).values()。还有就是,如果你愿意,可以使用非整数值来赋给枚举变量。使用.strs和.vals方法就行了。最后,注意重复变量名和值都是一种错误。有时你可能想有一个重复的值(比如为了创建别名)。你可以删掉抛出ValueError的那行,或者像这样用:vars(opcodes)[‘first_op’] = 0。这里我最不喜欢的是很有可能把vals和value搞混。也许我可以给vals想一个更好的名字。

15 Q: 为什么Python中没有”集合(Set)”类型?

当这个问题第一个发布在这里的时候还没有, 程序员们通常用字典来代替它. 但是在Python 2.4中有一个很好的set类型

16 Q: 我能用布尔类型吗?

当这个问题第一次发布在这里时,Python中还没有布尔类型。现在嘛,Python 2.3以后都内建有一个bool类型

17 Q: Python中有能与(test?result:alternative)等价的操作吗?

Java和C++都有三目运算符(test?result:alternative)。Python一直拒绝它,但在将来的Python 2.5中,将允许(result if test else alternative)形式的表达式。这样的结果是破坏了Python中表达式和语句清楚的区别,不过它是对许多人要求的妥协。

在Python 2.5到来前,你怎么办?这里有几个选择:

  1. 你可以试试[alternaticve, result][test]. 注意如果alternative和result中有递归调用或者昂贵的操作的话, 这个方法不太好, 因为它们两个都会被求值. 如果test可以返回一个非布尔值, 那就下面这个
  2. [result, alternative][not test]. 这两个的可读性都很好.
  3. test and result or alternative 有人很习惯这样,有人却觉得它令人胡涂. 它只在能确认result非假后使用.
  4. (test and [result] or [alternative])[0] 避免了上面那个限制.
  5. [lambda: result, lambda: alternative]not not test摆脱了上面所有的限制(除了可读性), 但别跟人家说是我告诉你这样做的. 你甚至可以把它封装在一个函数里面. 公认的命名规范是, 对于模仿关键词的变量, 在后面跟一个下划线. 所以我们有:
  6. if_(test, result, lambda: alternative)

    这里我们定义:

def if_(test, result, alternative=None):
"If test is true, 'do' result, else alternative. 'Do' means call if callable."
if test:
if callable(result): result = result()
return result
else:
if callable(alternative): alternative = alternative()
return alternative
--------------------------------------------------
>>> fact = lambda n: if_(n <= 1, 1, lambda: n * fact(n-1))
>>> fact(6)
720
  1. 现在假定你因为某种原因, 与”if(test, …”的语法相比, 就是更喜欢”if(test) …”(并且, 你从来不想摆脱alternative那个部分). 你可以试试这个:

def _if(test):
return lambda alternative: \
lambda result: \
[delay(result), delay(alternative)][not not test]() def delay(f):
if callable(f): return f
else: return lambda: f
>>> fact = lambda n: _if (n <= 1) (1) (lambda: n * fact(n-1))
>>> fact(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L

If u cn rd ths, u cn gt a jb in fncnl prg (if thr wr any)。(这个就不翻了吧:) )

18 Q: 还有其他主要类型是Python缺少的吗?

关于Python,有一件很爽的事情就是你可以使用数字,字符串,列表,和字典(现在还有集合和布尔)就能走很远。但是还有几个主要类型是缺少的. 对我来说,最重要的是一个可变的字符串。一次又一次的使用str += x 是很慢的,而维护字符组成的列表(或者子字符串的列表)意味着你放弃了一些很棒的字符串函数。一个可能的解决是array.array(‘c’)。另一个是UserString.MutableString,尽管它本来的目的是用于教学而不是实践。第三个是mmap模块, 第四是cStringIO. 这些方法都不完美,不过加在一起也提供了足够的选择。最后,我发现我经常需要一个某种顺序的队列。标准库中有一个 Queue module,但它是专用于线程的队列。因为这里有太多选项了,所以我就不为了实现一个标准队列的去游说了。不过呢,我将提供我实现的几种队列,FIFO,LIFO和优先队列:

"""
This module provides three types of queues, with these constructors:
Stack([items]) -- Create a Last In First Out queue, implemented as a list
Queue([items]) -- Create a First In First Out queue
PriorityQueue([items]) -- Create a queue where minimum item (by <) is first
Here [items] is an optional list of initial items; if omitted, queue is empty.
Each type supports the following methods and functions:
len(q) -- number of items in q (also q.__len__())
q.append(item) -- add an item to the queue
q.extend(items) -- add each of the items to the queue
q.pop() -- remove and return the "first" item from the queue
""" def Stack(items=None):
"A stack, or last-in-first-out queue, is implemented as a list."
return items or [] class Queue:
"A first-in-first-out queue."
def __init__(self, items=None): self.start = 0; self.A = items or []
def __len__(self): return len(self.A) - self.start
def append(self, item): self.A.append(item)
def extend(self, items): self.A.extend(items) def pop(self):
A = self.A
item = A[self.start]
self.start += 1
if self.start > 100 and self.start > len(A)/2:
del A[:self.start]
self.start = 0
return item class PriorityQueue:
"A queue in which the minimum element (as determined by cmp) is first."
def __init__(self, items=None, cmp=operator.lt):
self.A = []; self.cmp = cmp;
if items: self.extend(items) def __len__(self): return len(self.A) def append(self, item):
A, cmp = self.A, self.cmp
A.append(item)
i = len(A) - 1
while i > 0 and cmp(item, A[i//2]):
A[i], i = A[i//2], i//2
A[i] = item def extend(self, items):
for item in items: self.append(item) def pop(self):
A = self.A
if len(A) == 1: return A.pop()
e = A[0]
A[0] = A.pop()
self.heapify(0)
return e def heapify(self, i):
"Assumes A is an array whose left and right children are heaps,"
"move A[i] into the correct position. See CLR&S p. 130"
A, cmp = self.A, self.cmp
left, right, N = 2*i + 1, 2*i + 2, len(A)-1
if left <= N and cmp(A[left], A[i]):
smallest = left
else:
smallest = i
if right <= N and cmp(A[right], A[smallest]):
smallest = right
if smallest != i:
A[i], A[smallest] = A[smallest], A[i]
self.heapify(smallest)

注意一个技巧”items or []”,下面这样做是非常错误的

def Stack(items=[]): return items

这是想说明默认值是一个空的列表。如果我们这样作了,那么不同的堆栈将会共享一个列表。通过使默认值为None(一个有效输入之外的false值),我们可以安排每个实例得到它自己的新列表。可能拒绝使用这个技巧的理由,在下面例子中,一个用户这样用

s = Stack(items)

他可能觉得之后的s和items应该是相同的。但这是只会在发生在当items非空的时候。我认为这样的反对理由是不太严重的,因为这里并没有什么明确的承诺。(事实上,一个用户也可能期望items保持不变,这只在item为空时候成立)。

19 Q: 在Python里面怎么实现Singleton模式?

我假定你的意思是:你希望一个类只可以被实例化一次,然后当你再次实例化时抛出一个异常。我知道的最简单的办法是定义一个函数施行这个想法,然后在你的类构造函数里面调用这个函数:

def singleton(object, instantiated=[]):
"Raise an exception if an object of this class has been instantiated before."
assert object.__class__ not in instantiated, \
"%s is a Singleton class but is already instantiated" % object.__class__
instantiated.append(object.__class__) class YourClass:
"A singleton class to do something ..."
def __init__(self, args):
singleton(self)
...

你也可以跟metaclass打交道,这样你可以写出class YourClass(Singletion),但是为什么自找麻烦呢?在”四人帮”把理论带给我们以前,”singleton”(没有那个公式化的名字)只是一个简单的想法,刚好与一行简单代码相配,而不是一套信仰.

20 Q: 没有”news”是好消息吗?

我假设你的意思是Python没有new关键词。的确是的。在C++中,new用来标记堆的分配而不是栈的。这时,这个关键词是有用的。在Java中,所有的对象都是在堆上分配的,所以new没有真正的意义。它只是作为一个区别构造函数和其他静态方法的提醒。但是这个区别可能对Java弊大于利,因为它是低层次的,它强迫实现代码过早决定那些真正应该延后的东西。我想Python作出了正确的选择,保持构造函数和一个普通函数调用使用相同的语法。

比如说,在有bool类出现之前,我们曾经想实现一个。为了跟内建的有所区别的,我们就叫它Bool。假设我们想实现这样的想法:Bool类型只有一个true和一个false对象。一个办法是把类名从Bool改为_Bool(这样它不会被导出),然后定义一个函数Bool:

def Bool(val):
if val: return true
else: return false true, false = _Bool(1), _Bool(0)

这就让函数Bool变成_Bool对象的一个工厂(诚然是一个小得少见的工厂)。要点在于调用Bool(1)的程序员不应该知道或者关心返回的对象是一个新的还是回收的(至少对于不可变对象是这样)。Python语法允许隐藏这个区别,但是Java语法不行。

在一些著作中这里有点混淆。有些人使用术语”Singleton Pattern”称呼这样的工厂,因为这里对构造函数的每个不同的参数有一个单独的对象。和大多数人一样,我赞同前一个问题中我下的定义。这个模式也可以封装一个类型。我们可以叫它”CachedFactory”。这个想法来源于当你写下:

class Bool:
... ## see here for Bool's definition Bool = CachedFactory(Bool)

然后当你第一次调用Bool(1),参数列表(1,),得到原来的Bool类的代理。但是任何后续的对Bool(1)调用将返回第一个对象,它是被保存在缓存中:

class CachedFactory:
def __init__(self, klass):
self.cache = {}
self.klass = klass def __call__(self, *args):
if self.cache.has_key(args):
return self.cache[args]
else:
object = self.cache[args] = self.klass(*args)
return object

需要注意的一件事情是,类和构造函数没有任何其余的东西。这个模式将适用于所有可调用的对象。当扩展到普通的函数,它被称作”Memoization Pattern”。实现代码是一样的,只是名字变了:

class Memoize:
def __init__(self, fn):
self.cache = {}
self.fn = fn def __call__(self, *args):
if self.cache.has_key(args):
return self.cache[args]
else:
object = self.cache[args] = self.fn(*args)
return object

现在你可以写下fact = Memoize(fact),现在阶乘运算的时间复杂度是分摊到每次调用的O(1),而不是O(n)。

21 Q: 我能有一个像shell里面一样的历史记录吗?

能。如果你要是这个么?

>>> from shellhistory import h
h[2] >>> 7*8
56
h[3] >>> 9*9
81
h[4] >>> h[2]
56
h[5] >>> 'hello' + ' world'
'hello world'
h[6] >>> h
[None, 9, 56, 81, 56, 'hello world']
h[7] >>> h[5] * 2
'hello worldhello world'
h[8] >>> h[7] is _ is h[-1]
1

这是怎么办到的?变量sys.ps1是系统提示符,默认值是字符串’>>>’,但是你可以设置成其它任何东西。如果你设置了一个非字符串对象,这个对象的str方法将被调用。所以我们将创建这么一个对象,它的字符串方法把最近的结果(变量)添加到一个叫h(代表history)的列表中, 然后返回一个包含列表长度,接着是’>>>’的提示字符串。至少原来计划是这样。结果是(在IDLE 2.2的Windows实现中),sys.ps1.str被调用了三次,而不是提示符被打印前的一次。别问我为什么。为了解决这个问题,只有当不是历史列表中最后一个元素时,我才加入它。而且我也不自讨麻烦的把None加入历史列表中了,因为它不会被Python的交互循环显示。我还排除了向h自己中添加h,因为这样的环形结构可以能会带来打印和比较时的麻烦。另一个复杂因素是Python解释器实际上是尝试打印’\n’ + sys.ps1,(它本来应该单独的打印’\n’,或者打印’\n’ + str(sys.ps1))这就意味着sys.ps1也需要一个radd方法. 最后,如果Python session中(或者是在.python启动文件中)一开始的输入是导入我的第一版模块,它将会失败。在检查了一番之后,我发现这是因为直到第一个表达式被求值以后,变量才被绑定。所以我捕获了未绑定的异常。然后就有:

import sys

h = [None]

class Prompt:
"Create a prompt that stores results (i.e. _) in the array h."
def __init__(self, str='h[%d] >>> '):
self.str = str; def __str__(self):
try:
if _ not in [h[-1], None, h]: h.append(_);
except NameError:
pass
return self.str % len(h); def __radd__(self, other):
return str(other) + str(self) sys.ps1 = Prompt()

22 Q: 怎么得到我的函数的运行时间?

下面是一个简单的答案:

def timer(fn, *args):
"Time the application of fn to args. Return (result, seconds)."
import time
start = time.clock()
return fn(*args), time.clock() - start
>>>timer(max, range(1e6))
(999999, 0.4921875)

在我的utils module里还有一个更复杂的答案。

23 Q: 我的.python启动文件是什么样子的?

现在它是看起来像这样,但是它已经改变了很多了:

from __future__ import nested_scopes
import sys, os, string, time
from utils import * ################ Interactive Prompt and Debugging ################ try:
import readline
except ImportError:
print "Module readline not available."
else:
import rlcompleter
readline.parse_and_bind("tab: complete") h = [None] class Prompt:
def __init__(self, str='h[%d] >>> '):
self.str = str; def __str__(self):
try:
if _ not in [h[-1], None, h]: h.append(_);
except NameError:
pass
return self.str % len(h); def __radd__(self, other):
return str(other) + str(self) if os.environ.get('TERM') in [ 'xterm', 'vt100' ]:
sys.ps1 = Prompt('\001\033[0:1;31m\002h[%d] >>> \001\033[0m\002')
else:
sys.ps1 = Prompt()
sys.ps2 = ''

原作者: [Peter Norvig](mailto: peter@norvig.com)

原地址: Origin

中文版: 中文翻译

Python IAQ中文版 - Python中少有人回答的问题的更多相关文章

  1. .net框架中少有人知的扩展cmod

    最近在利用metadata api抽取.net的原数据信息,发现了不少“坑”,也发现了不少常年用着c#的人都不知道的扩展. 说到.net原数据的可扩展性,第一个让人能想到的就是CustomAttrib ...

  2. Python爬虫抓取 python tutorial中文版,保存为word

    看到了中文版的python tutorial,发现是网页版的,刚好最近在学习爬虫,想着不如抓取到本地 首先是网页的内容 查看网页源码后发现可以使用BeautifulSoup来获取文档的标题和内容,并保 ...

  3. 如何使用Python在Kaggle竞赛中成为Top15

    如何使用Python在Kaggle竞赛中成为Top15 Kaggle比赛是一个学习数据科学和投资时间的非常的方式,我自己通过Kaggle学习到了很多数据科学的概念和思想,在我学习编程之后的几个月就开始 ...

  4. python matplotlib plot 数据中的中文无法正常显示的解决办法

    转发自:http://blog.csdn.net/laoyaotask/article/details/22117745?utm_source=tuicool python matplotlib pl ...

  5. 人们对Python在企业级开发中的10大误解

    From : 人们对Python在企业级开发中的10大误解 在PayPal的编程文化中存在着大量的语言多元化.除了长期流行的C++和Java,越来越多的团队选择JavaScript和Scala,Bra ...

  6. Python学习书籍推荐 中英对照

    Learn Python The Hard Way 笨办法学 python A Byte of Python 简明Python教程 初学者 英文版 Learning Python, 3rd Editi ...

  7. python——深刻理解Python中的元类(metaclass)

    译注:这是一篇在Stack overflow上 很热的帖子.提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解.他知道这肯定和自省有关,但仍然觉 ...

  8. 孤荷凌寒自学python第五十二天初次尝试使用python读取Firebase数据库中记录

    孤荷凌寒自学python第五十二天初次尝试使用python读取Firebase数据库中记录 (完整学习过程屏幕记录视频地址在文末) 今天继续研究Firebase数据库,利用google免费提供的这个数 ...

  9. 孤荷凌寒自学python第三十二天python的代码块中的异常的捕获

    孤荷凌寒自学python第三十二天python的代码块中的异常的捕获 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 今天简单了解了Python的错误陷阱,了解到其与过去学过的其它语言非常类似 ...

随机推荐

  1. CSS 之Grid 网格知识梳理2

    继上篇的CSS 之Grid下半部分   14.将单元格划分到一个区域,使用grid-template-areas属性: ag: grid-template-areas: "header  h ...

  2. 逆向破解之160个CrackMe —— 025

    CrackMe —— 025 160 CrackMe 是比较适合新手学习逆向破解的CrackMe的一个集合一共160个待逆向破解的程序 CrackMe:它们都是一些公开给别人尝试破解的小程序,制作 c ...

  3. 使用Docker快速部署ELK分析Nginx日志实践(二)

    Kibana汉化使用中文界面实践 一.背景 笔者在上一篇文章使用Docker快速部署ELK分析Nginx日志实践当中有提到如何快速搭建ELK分析Nginx日志,但是这只是第一步,后面还有很多仪表盘需要 ...

  4. 第10章 文档对象模型DOM 10.2 Document类型

    Document 类型 JavaScript 通过 Document 类型表示文档.在浏览器中, document 对象是 HTMLDocument (继承自 Document 类型)的一个实例,表示 ...

  5. GRE Words Revenge AC自动机 二进制分组

    GRE Words Revenge 题意和思路都和上一篇差不多. 有一个区别就是需要移动字符串.关于这个字符串,可以用3次reverse来转换, 前面部分翻转一下, 后面部分翻转一下, 最后整个串翻转 ...

  6. bzoj 2001 CITY 城市建设 cdq分治

    题目传送门 题解: 对整个修改的区间进行分治.对于当前修改区间来说,我们对整幅图中将要修改的边权都先改成-inf,跑一遍最小生成树,然后对于一条树边并且他的权值不为-inf,那么这条边一定就是树边了. ...

  7. HDU-1532 网络流裸题

    HDU-1532 题意简单的来说就是从1点到n点,最大的流量是多少. 代码: #include<bits/stdc++.h> using namespace std; #define Fo ...

  8. poj 2649 Factovisors 对n!进行因数分解

    Factovisors Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 4431   Accepted: 1086 Descr ...

  9. 通过原型继承理解ES6 extends 如何实现继承

    前言 第一次接触到 ES6 中的 class 和 extends 时,就听人说这两个关键字不过是语法糖而已.它们的本质还是 ES3 的构造函数,原型链那些东西,没有什么新鲜的,只要理解了原型链等这些概 ...

  10. 编写一个函数来反转某个二进制型里的字节顺序(erlang)

    reverse_byte(<<>>) -> <<>>; reverse_byte(<<Header:8, Tail/bits>& ...