<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # 第十九章 更多功能 我在本書中的一個目標就是盡量少教你 Python(譯者注:而要多教編程)。有的時候完成一個目的有兩種方法,我都會只選擇一種而不提其他的。或者有的時候我就把第二個方法放到練習里面。 現在我就要往回倒車一下,撿起一些當時略過的重要內容來給大家講一下。Python 提供了很多并非必須的功能—你完全可以不用這些功能也能寫出很好的代碼—但用這些功能有時候能讓你的代碼更加簡潔,可讀性更強,或者更有效率,甚至有時候能兼顧這三個方面。 ## 19.1 條件表達式 在 5.4 中,我們見到了條件語句。條件語句往往用于二選一的情況下;比如: ```py if x > 0: y = math.log(x) else: y = float('nan') ``` 這個語句檢查了 x 是否為正數。如果為正數,程序就計算對數值 math.log。如果非正,對數函數會返回一個值錯誤 ValueError。要避免因此而導致程序異常退出,咱們就生成了一個『NaN』,這個符號是一個特別的浮點數的值,表示的意思是『不是一個數』。 用一個條件表達式能讓這個語句更簡潔: ```py y = math.log(x) if x > 0 else float('nan') ``` 上面這行代碼讀起來就跟英語一樣了:『如果 x 大于 0 就讓 y 等于 x 的對數;否則的話就返回 Nan』。 遞歸函數有時候也可以用這種條件表達式來改寫。例如下面就是分形函數 factorial 的一個遞歸版本: ```py def factorial(n): if n == 0: return 1 else: return n * factorial(n-1) ``` 我們可以這樣改寫: ```py def factorial(n): return 1 if n == 0 else return n * factorial(n-1) ``` 條件表達式還可以用于處理可選參數。例如下面就是練習 2 中 GoodKangaroo 類的 init 方法: ```py def __init__(self, name, contents=None): self.name = name if contents == None: contents = [] self.pouch_contents = contents ``` 我們可以這樣來改寫: ```py def __init__(self, name, contents=None): self.name = name self.pouch_contents = [] if contents == None else contents ``` 一般來講,你可以用條件表達式來替換掉條件語句,無論這些語句的分支是返回語句或者是賦值語句。 ## 19.2 列表推導 在 10.7 當中,我們看到了映射和過濾模式。例如,下面這個函數接收一個字符串列表,然后將每一個元素都用字符串方法 capitalize 處理成大寫的,然后返回一個新的字符串列表: ```py def capitalize_all(t): res = [] for s in t: res.append(s.capitalize()) return res ``` 用列表推導就可以將上面的代碼寫得更簡潔: ```py def capitalize_all(t): return [s.capitalize() for s in t] ``` 方括號的意思是我們正在建立一個新的列表。方括號內的表達式確定了此新列表中的元素,然后 for 語句表明我們要遍歷的序列。 列表推導的語法有點復雜,就因為這個循環變量,在上面例子中是 s,這個 s 在我們定義之前就出現在語句中了。 列表推導也可以用到濾波中。例如,下面的函數從 t 中選擇了大寫的元素,然后返回成一個新的列表: ```py def only_upper(t): res = [] for s in t: if s.isupper(): res.append(s) return res ``` 咱們可以用列表推導來重寫這個函數: ```py def only_upper(t): return [s for s in t if s.isupper()] ``` 列表推導很簡潔,也很容易閱讀,至少在簡單的表達式上是這樣。這些語句的執行也往往比同樣情況下的 for 循環更快一些,有時候甚至快很多。所以如果你因為我沒有早些給你講而發怒,我也能理解。 但是,我也要辯護一下,列表推導會導致調試非常困難,因為你不能在循環內部放 print 語句了。我建議你只去在一些簡單的地方使用,要確保你第一次寫出來就能保證代碼正常工作。也就是說初學者就還是別用為好。 ## 19.3 生成器表達式 生成器表達式與列表推導相似,用的不是方括號,而是圓括號: ```py >>> g = (x**2 for x in range(5)) >>> g <generator object <genexpr> at 0x7f4c45a786c0> ``` 上面這樣運行得到的結果就是一個生成器對象,用來遍歷一個值的序列。但與列表推導不同的是,生成器表達式并不會立即計算出所有的值;它要等著被調用。內置函數 next 會從生成器中得到下一個值: ```py >>> next(g) 0 >>> next(g) 1 ``` 當程序運行到序列末尾的時候,next 函數就會拋出一個停止遍歷的異常。你也可以用一個 for 循環來遍歷所有的值: ```py >>> for val in g: ... print(val) 4 9 16 ``` 生成器對象能夠追蹤在序列中的位置,所以 for 語句就會在 next 函數退出的地方開始。一旦生成器使用完畢了,接下來就要拋出一個停止異常了: ```py >>> next(g) StopIteration ``` 生成器表達式多用于求和、求最大或者最小這樣的函數中: ```py >>> sum(x**2 for x in range(5)) 30 ``` ## 19.4 any 和 all Python 提供了一個名為 any 的內置函數,該函數接收一個布爾值序列,只要里面有任意一個是真,就返回真。該函數適用于列表: ```py >>> any([False, False, True]) True ``` 但這個函數多用于生成器表達式中: ```py >>> any(letter == 't' for letter in 'monty') True ``` 這個例子沒多大用,因為效果和 in 運算符是一樣的。但我們能用 any 函數來改寫我們在 9.3 中寫的一些搜索函數。例如,我們可以用如下的方式來改寫 avoids: ```py def avoids(word, forbidden): return not any(letter in forbidden for letter in word) ``` 這樣這個函數讀起來基本就跟英語一樣了。 用 any 函數和生成器表達式來配合會很有效率,因為只要發現真值程序就會停止了,所以并不需要對整個序列進行運算。 Python 還提供了另外一個內置函數 all,該函數在整個序列都是真的情況下才返回真。 做個練習,用 all 來改寫一下 9.3 中的 uses_all 函數。 ## 19.5 集合 在 13.6 中,我用了字典來查找存在于文檔中而不存在于詞匯列表中的詞匯。我寫的這個函數接收兩個參數,一個是 d1 是包含了文檔中的詞作為鍵,另外一個是 d2 包含了詞匯列表。程序會返回一個字典,這個字典包含的鍵存在于 d1 而不在 d2 中。 ```py def subtract(d1, d2): res = dict() for key in d1: if key not in d2: res[key] = None return res ``` 在這些字典中,鍵值都是 None,因為根本沒有使用。結果就是,浪費了一些存儲空間。 Python 還提供了另一個內置類型,名為 set(也就是集合的意思),其性質就是有字典的鍵而無鍵值。 對集合中添加元素是很快的;對集合成員進行檢查也很快。此外集合還提供了一些方法和運算符來進行常見的集合運算。 例如,集合的減法就可以用一個名為 difference 的方法,或者就用減號-。所以我們可以把 subtract 改寫成如下形式: ```py def subtract(d1, d2): return set(d1) - set(d2) ``` 上面這個函數的結果就是一個集合而不是一個字典,但對于遍歷等等運算來說,用起來都一樣的。 本書中的一些練習都可以通過使用集合而改寫成更精簡更高效的形式。例如,下面的代碼就是 has_duplicates 的一個實現方案,來自練習 7,用的是字典: ```py def has_duplicates(t): d = {} for x in t: if x in d: return True d[x] = True return False ``` 當一個元素第一次出現的時候,就被添加到字典中。如果同一個元素又出現了,該函數就返回真。 用集合的話,我們就能把該函數寫成如下形式: ```py def has_duplicates(t): return len(set(t)) < len(t) ``` 一個元素在一個集合中只能出現一次,所以如果一個元素在 t 中出現次數超過一次,集合會比 t 規模小一些。如果沒有重復,集合的規模就應該和 t 一樣大。 我們還能用集合來做一些第九章的練習。例如,下面就是用一個循環實現的一個版本的 uses_only: ```py def uses_only(word, available): for letter in word: if letter not in available: return False return True ``` uses_only 會檢查 word 中的所有字母是否出現在 available 中。我們可以用如下方法重寫: ```py def uses_only(word, available): return set(word) <= set(available) ``` 這里的<=運算符會檢查一個集合是否切另外一個集合的子集或者相等,如果 word 中所有的字符都出現在 available 中就返回真。 ## 19.6 計數器 計數器跟集合相似,除了一點,就是如果計數器中元素出現的次數超過一次,計數器會記錄下出現的次數。如果你對數學上多重集的概念有所了解,就會知道計數器是一種對多重集的表示方式。 計數器定義在一個名為 collections 的標準模塊中,所以你必須先導入一下。你可以用字符串,列表或者任何支持遍歷的類型來初始化一個計數器: ```py >>> from collections import Counter >>> count = Counter('parrot')>>> count Counter({'r': 2, 't': 1, 'o': 1, 'p': 1, 'a': 1}) ``` 計數器的用法與字典在很多方面都相似;二者都映射了每個鍵到出現的次數上。在字典中,鍵必須是散列的。 與字典不同的是,當你讀取一個不存在的元素的時候,計數器并不會拋出異常。相反的,這時候程序會返回 0: ```py >>> count['d'] 0 ``` 我們可以用計數器來重寫一下練習 6 中的這個 is_anagram 函數: ```py def is_anagram(word1, word2): return Counter(word1) == Counter(word2) ``` 如果兩個單詞是換位詞,他們包含同樣個數的同樣字母,所以他們的計數器是相等的。 、計數器提供了一些方法和運算器來運行類似集合的運算,包括加法,剪發,合并和交集等等。此外還提供了一個最常用的方法,most_common,該方法會返回一個由值-出現概率組成的數據對的列表,按照概率從高到低排列: ```py >>> count = Counter('parrot') >>> for val, freq in count.most_common(3): ... print(val, freq) r 2 p 1 a 1 ``` ## 19.7 默認字典 collection 模塊還提供了一個默認字典,與普通字典的區別在于當你讀取一個不存在的鍵的時候,程序會添加上一個新值給這個鍵。 當你創建一個默認字典的時候,就提供了一個能創建新值的函數。用來創建新對象的函數也被叫做工廠。內置的創建列表、集合以及其他類型的函數都可以被用作工廠: ```py >>> from collections import defaultdict >>> d = defaultdict(list) ``` 要注意到這里的參數是一個列表,是一個類的對象,而不是 list(),帶括號的就是一個新列表了。這個創建新值的函數只有當你試圖讀取一個不存在的鍵的時候才會被調用。 ```py >>> t = d['new key'] >>> t [] ``` 新的這個我們稱之為 t 的列表,也會被添加到字典中。所以如果我們修改 t,這種修改也會在 d 中出現。 ```py >>> t.append('new value') >>> d defaultdict(<class 'list'>, {'new key': ['new value']}) ``` 所以如果你要用列表組成字典的話,你就可以多用默認字典來寫出更簡潔的代碼。你可以在[這里](http://thinkpython2.com/code/anagram_sets.py)下載我給練習 2 提供的樣例代碼,其中我建立了一個字典,字典中建立了從一個字母字符串到一個可以由這些字母拼成的單詞的映射。例如,『opst』就映射到了列表[’opts’, ’post’, ’pots’, ’spot’, ’stop’, ’tops’]。 下面就是原版的代碼: ```py def all_anagrams(filename): d = {} for line in open(filename): word = line.strip().lower() t = signature(word) if t not in d: d[t] = [word] else: d[t].append(word) return d ``` 用默認集合就可以簡化一下,就如你在練習 2 中用過的那樣: ```py def all_anagrams(filename): d = {} for line in open(filename): word = line.strip().lower() t = signature(word) d.setdefault(t, []).append(word) return d ``` 這個代碼有一個不足,就是每次都要建立一個新列表,而不論是否需要創建。對于列表來說,這倒不要緊,不過如果工廠函數比較復雜的話,這就麻煩了。 這時候咱們就可以用默認字典來避免這個問題并且簡化代碼: ```py def all_anagrams(filename): d = defaultdict(list) for line in open(filename): word = line.strip().lower() t = signature(word) d[t].append(word) return d ``` 你可以從[這里](http://thinkpython2.com/code/PokerHandSoln.py)下載我給練習 3 寫的樣例代碼,該代碼中在 has_straightflush 函數用的是默認集合。這份代碼的不足就在于每次循環都要創建一個 Hand 對象,而不論是否必要。做個練習,用默認字典來該寫一下這個程序。 ## 19.8 命名元組 很多簡單的類就是一些相關值的集合。例如在 15 章中定義的 Point 類中就包含兩個數值,x 和 y。當你這樣定義一個類的時候,你通常要寫一個 init 方法和一個 str 方法: ```py class Point: def __init__(self, x=0, y=0): self.x = x self.y = y def __str__(self): return '(%g, %g)' % (self.x, self.y) ``` 要傳達這么小規模的信息卻要用這么多代碼。Python 提供了一個更簡單的方式來做類似的事情: ```py from collections import namedtuple Point = namedtuple('Point', ['x', 'y']) ``` 第一個參數是你要寫的類的名字。第二個是 Point 對象需要有的屬性列表,為字符串。命名元組返回的值是一個類的對象。 ```py >>> Point <class '__main__.Point'> ``` Point 會自動提供諸如 init 和 str 之類的方法,所以就不用再去寫了。 要建立一個 Point 對象,你就可以用 Point 類作為一個函數用: ```py >>> p = Point(1, 2) >>> p Point(x=1, y=2) ``` init 方法把參數賦值給按照你設定來命名的屬性。 str 方法輸出整個 Point 類及其屬性的一個字符串表達。 你可以用名字來讀取命名元組中的元素: ```py >>> p.x, p.y (1, 2) ``` 但你也可以把命名元組當做元組來用: ```py >>> p[0], p[1] (1, 2) >>> x, y = p >>> x, y (1, 2) ``` 命名元組提供了定義簡單類的快捷方式。缺點就是這些簡單的類不能總保持簡單的狀態。有時候你可能想給一個命名元組添加方法。這時候你就得定義一個新類來繼承命名元組: ```py class Pointier(Point): # add more methods here ``` Or you could switch to a conventional class definition. >或者你可以把命名元組轉換成一個常規的類的定義。 ## 19.9 收集關鍵詞參數 在 12.4 中,我們已經學過了如何寫將參數收集到一個元組中的函數: ```py def printall(*args): print(args) ``` 這種函數可以用任意數量的位置參數(就是無關鍵詞的參數)來調用。 ```py >>> printall(1, 2.0, '3') (1, 2.0, '3') ``` 但*運算符并不能收集關鍵詞參數: ```py >>> printall(1, 2.0, third='3') TypeError: printall() got an unexpected keyword argument 'third' ``` To gather keyword arguments, you can use the ** operator: >要收集關鍵詞參數,你就可以用**運算符: ```py def printall(*args, **kwargs): print(args, kwargs) ``` 你可以用任意名字來命名這里的關鍵詞收集參數,不過通常大家都用 kwargs。得到的結果是一個字典,映射了關鍵詞鍵名與鍵值: ```py >>> printall(1, 2.0, third='3') >>> (1, 2.0) {'third': '3'} ``` 如果你有一個關鍵詞和值組成的字典,你就可以用散射運算符,**來調用一個函數: ```py >>> d = dict(x=1, y=2) >>> Point(**d) Point(x=1, y=2) ``` 不用散射運算符的話,函數會把 d 當做一個單獨的位置參數,所以就會把 d 賦值股額 x,然后出錯,因為沒有給 y 賦值: ```py >>> d = dict(x=1, y=2) >>> Point(d) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: __new__() missing 1 required positional argument: 'y' ``` 當你寫一些有大量參數的函數的時候,就可以創建和使用一些字典,這樣能把各種常用選項弄清。 ## 19.10 Glossary 術語列表 conditional expression: An expression that has one of two values, depending on a condition. >條件表達式:一種根據一個條件來從兩個值中選一個的表達式。 list comprehension: An expression with a for loop in square brackets that yields a new list. >列表推導:一種用表達式, 方括號內有一個 for 循環,生成一個新的列表。 generator expression: An expression with a for loop in parentheses that yields a generator object. >生成器表達式:一種表達式,圓括號內放一個 for 循環,產生一個生成器對象。 multiset: A mathematical entity that represents a mapping between the elements of a set and the number of times they appear. >多重集:一個數學上的概念,表示了一種從集合中元素到出現次數只見的映射關系。 factory: A function, usually passed as a parameter, used to create objects. >工廠:一個函數,通常作為參數傳遞,用來產生對象。 ## 19.11 練習 ### 練習 1 下面的函數是遞歸地計算二項式系數的。 ```py def binomial_coeff(n, k): """Compute the binomial coefficient "n choose k". n: number of trials k: number of successes returns: int """ if k == 0: return 1 if n == 0: return 0 res = binomial_coeff(n-1, k) + binomial_coeff(n-1, k-1) return res ``` 用網狀條件語句來重寫一下函數體。 一點提示:這個函數并不是很有效率,因為總是要一遍一遍地計算同樣的值。你可以通過存儲已有結果(參考 11.6)來提高效率。但你會發現如果你用條件表達式實現,就會導致這種記憶更困難。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看