Python IAQ:
|
| Q: 什么是"少有回答的问题(Infrequently Answered Question)" ? |
一个问题之所以很少有人回答, 要么是因为很少有人知道问题的答案, 要么是因为它涉及到一个晦涩而隐蔽的知识点(但可能是你关心的). 我过去认为是我在Java IAQ中发明了这个词组, 但是它也出现在了以资料丰富而著称的 About.com Urban Legends网站上. 关于Python的FAQ有很多, 但是Python的IAQ只有这一个. ("少见问题列表"倒是有一些, 其中一个是有讽刺意味的 C.)
| 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 ..."
|
| 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的类型(比如序列和数)的定义不太符合规则.
| Q: 在Python中我能写++x和x++吗? |
从语法上说,++x能, x++不能. 但是从实际使用来说,别这样做. 这么说什么意思?
进一步的问题: 为什么Python不允许 x++? 我相信原因与Python不允许在表达式中赋值一样: Python想要清晰的区分语句和表达式. 如果我觉得这两者应该有所区别, 那么不允许++就是最好的决定. 另一方面, 函数语言的鼓吹者认为语句就应该是表达式. 我跟我的丹麦老乡, Bjarne Stroustrup, 都这样认为. 他在The Design and Evolution of C++中说:“如果是从头来设计一种语言的话,我会按照Algol68的方式, 让每条语句和声明都是一个有返回值的表达式”.
| 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'
|
(本文中所有的文件中的代码都在横线以上,使用这些代码的例子在横线以下.) 这样你就可以使用一种不同的语法了,但是它不能给你带来一种新的输出格式,它只是把Python中以有str的格式封装了一层而已。这个做法很像Java里面的toString()格式。 C++使用的是一种迥异的格式:它没有定义一组把对象转换为字符串的规则,而定义了一种把对象打印到流的规则(也许是不完整的规则,因为很多C++程序仍然使用printf). 用流来实现会更加复杂,但是它的优势在于如果你需要打印一个相当巨大的对象,就不用创建一个巨大的临时对象来做这件事。
| 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,
|
| 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!--译注)
| Q: 那么,对象有没有类似的简便办法呢? |
的确是有的. 如果你想要创建一个对象来把数据保存在不同的域中, 下面的代码就可以做到:
class Struct:
def __init__(self, **entries): self.__dict__.update(entries)
|
从本质上说,我们在这里做的是创建一个匿名类。好吧,我知道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)
|
| 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)
|
| 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))
|
值得注意的是,如果没 有DefaultDict, bigram例子程序中的d[w1][w2] += 1就大概应该象这样:
d.setdefault(w1,{}).setdefault(w2, 0); d[w1][w2] += 1
| 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))
|
| 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
|
所以这个问题的答案是: 你可以在方法调用中使用*y或**y(或者其他任何你可以放在函数调用中的), 因为方法调用就是函数调用.
| Q: 你能用用0行代码实现Python的抽象类吗? 4行呢? |
Java中有一个abstract关键词. 你可以用它来定义一个只能继承不能被实例化的抽象类, 该类中所有的抽象方法都需要你来实现. 很少有人知道在Python中, 你可以用几乎一样的方式使用abstract. 不同的是, 当你想要调用一个没有实现的方式时, 你得到的是一个运行时错误而不是编译错误. 比较下面的代码:
## Python
class MyAbstractClass:
def method1(self): abstract
class MyClass(MyAbstractClass):
pass
| /* Java */
public abstract class MyAbstractClass {
public abstract void method1();
}
class MyClass extends 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')
|
| Q: 在Python中我怎么实现枚举类型呢? |
这个问题没有一个答案, 因为在Python中有好几个答案, 取决于你对枚举的期望. 如果你只是想有几个变量, 每个都有不同的整数值, 你可以这样:
red, green, blue = range(3) |
缺点是当你想在左边添加一个新的变量, 需要同时增加右边的整数. 不过这不算太坏, 因为当你忘记的时候Python会抛出一个错误. 如果你把枚举隔离在类中可能更干净一点:
class Colors:
red, green, blue = range(3) |
现在Colors.red会得到0, 并且dir(Colors)可能也能派上用场(虽然你还需要忽略__doc__和__module__两项). 如果你想完全控制每个枚举变量的值, 可以使用好几个问题以前的Struct函数, 就像下面:
Enum = Struct Colors = Enum(red=0, green=100, blue=200) |
尽管这些简单的办法通常已经够了, 可有人还想要更多. 在 python.org, ASPN 和 faqts上都有枚举类型的实现. 下面是我的版本, 它(几乎)涵盖所有人的需要,并且仍然保持合理的简洁(一共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想一个更好的名字.
| Q: 为什么Python中没有"集合(Set)"类型? |
当这个问题第一个发布在这里的时候还没有, 程序员们通常用字典来代替它. 但是在Python 2.4中有一个很好的内建set 类型.
| Q: 我能用布尔类型吗? |
当这个问题第一次发布在这里时, Python中还没有布尔类型. 现在嘛, Python 2.3以后都内建有一个 bool 类型.
| Q: Python中有能与(test?result:alternative)等价的操作吗? |
Java和C++都有三目运算符(test?result:alternative). Python一直拒绝它, 但在将来的Python 2.5中, 将允许(result if test else alternative)形式的表达式. 这样的结果是破坏了Python中表达式和语句清楚的区别, 不过它是对许多人要求的妥协.
在Python 2.5到来前, 你怎么办? 这里有几个选择:
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
|
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
|
If u cn rd ths, u cn gt a jb in fncnl prg (if thr wr any). ( 这个就不翻了吧:) )
| 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为空时候成立).
| 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"(没有那个公式化的名字)只是一个简单的想法, 刚好与一行简单代码相配, 而不是一套信仰.
| 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).
| 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() |
| 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
|
在我的utils module里还有一个更复杂的答案.
| 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 = '' |
感谢Amit J. Patel, Max M, Dan Winkler, Chih-Chung Chang, Bruce Eckel, Kalle Svensson, Mike Orr, Steven Rogers 和其它贡献了想法和修改意见的人.
Peter Norvig
本中文译本得到了Peter Norvig先生的许可。
2006-06-18 第一稿翻译完成