Python 中少有人回答的问题
The Python IAQ: Infrequently Answered Questions
1 Q: 什么是 "少有人回答的问题(Infrequently Answered Question)" ?
一个问题之所以很少有人回答, 要么是因为很少有人知道问题的答案, 要么是因为它涉及到一个晦涩而隐蔽的知识点(但可能是你关心的). 我过去认为是我在 Java IAQ http://www.norvig.com/java-iaq.html 中发明了这个词组, 但是它也出现在了以数据丰富而著称的 About.com Urban Legends 网站上. 关于 Python 的 FAQ 有很多, 但是 Python 的 IAQ 只有这一个.("少见问题列表" 倒是有一些, 其中一个是有讽刺意味的 C http://www.plethora.net/~seebs/faqs/c-iaq.html .)
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)可能也能派上用场(虽然你还需要忽略 doc 和 module 两项). 如果你想完全控制每个枚举变量的值, 可以使用好几个问题以前的 Struct 函数, 就像下面:
- Enum = Struct
- Colors = Enum(red=0, green=100, blue=200)
尽管这些简单的办法通常已经够了, 可有人还想要更多. 在, 和上都有枚举类型的实现. 下面是我的版本, 它 (几乎) 涵盖所有人的需要, 并且仍然保持合理的简洁(一共 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 类型 http://docs.python.org/lib/types-set.html .
16 Q: 我能用布尔类型吗?
当这个问题第一次发布在这里时, Python 中还没有布尔类型. 现在嘛, Python 2.3 以后都内建有一个 bool 类型 http://docs.python.org/lib/node31.html .
17 Q: Python 中有能与 (test?result:alternative) 等价的操作吗?
Java 和 C++ 都有三目运算符 (test?result:alternative).Python 一直拒绝它, 但在将来的 Python 2.5 中, 将允许(result if test else alternative) 形式的表达式. 这样的结果是破坏了 Python 中表达式和语句清楚的区别, 不过它是对许多人要求的妥协.
在 Python 2.5 到来前, 你怎么办? 这里有几个选择:
你可以试试[alternaticve, result][test]. 注意如果 alternative 和 result 中有递归调用或者昂贵的操作的话, 这个方法不太好, 因为它们两个都会被求值. 如果 test 可以返回一个非布尔值, 那就下面这个
[result, alternative][not test]. 这两个的可读性都很好.
test and result or alternative 有人很习惯这样, 有人却觉得它令人胡涂. 它只在能确认 result 非假后使用.
(test and [result] or [alternative])[0] 避免了上面那个限制.
[lambda: result, lambda: alternative][not not test]()摆脱了上面所有的限制(除了可读性), 但别跟人家说是我告诉你这样做的. 你甚至可以把它封装在一个函数里面. 公认的命名规范是, 对于模仿关键词的变量, 在后面跟一个下划线. 所以我们有:
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
现在假定你因为某种原因, 与 "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 = ''
来源: https://www.cnblogs.com/by87/p/python-iaq-cn.html