1. 程式人生 > >Python IAQ中文版 - Python中少有人回答的問題

Python IAQ中文版 - Python中少有人回答的問題

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)可能也能派上用場(雖然你還需要忽略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想一個更好的名字。

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
原地址: Origin

中文版: 中文翻