起步
這本書有21個章節(jié), 整理也是根據(jù)這些章節(jié)過來.
第一章: python數(shù)據(jù)模型
第二章: 序列構(gòu)成的數(shù)組
這部分主要是介紹序列, 著重介紹數(shù)組和元組的一些高級用法.
序列按照容納數(shù)據(jù)的類型可以分為:
容器序列
: list、tuple 和 collections.deque 這些序列能存放不同類型的數(shù)據(jù)
扁平序列
: str、bytes、bytearray、memoryview 和 array.array,這類序列只能容納一種類型.
如果按照是否能被修改可以分為:
可變序列
: list、bytearray、array.array、collections.deque 和 memoryview
不可變序列
: tuple、str 和 bytes
列表推導
列表推導是構(gòu)建列表的快捷方式, 可讀性更好且效率更高.
例如, 把一個字符串變成unicode的碼位列表的例子, 一般:
第三章: 字典和集合
defaultdict:處理找不到的鍵的一個選擇
當某個鍵不在映射里, 我們也希望也能得到一個默認值. 這就是 defaultdict , 它是 dict 的子類, 并實現(xiàn)了 __missing__ 方法.
不可變映射類型
說到不可變, 第一想到的肯定是元組, 但是對于字典來說, 要將key和value的對應(yīng)關(guān)系變成不可變, types 模塊的 MappingPrype 可以做到:
第四章: 文本和字節(jié)序列
本章討論了文本字符串和字節(jié)序列, 以及一些編碼上的轉(zhuǎn)換. 本章討論的 str指的是python3下的.
字符問題
如果字符序列和預(yù)期不符, 在進行解碼或編碼時容易拋出 Unicode*Error的異常. 造成這種錯誤是因為目標編碼中沒有定義某個字符(沒有定義某個碼位對應(yīng)的字符), 這里說說解決這類問題的方式.
unicode文本排序
對于字符串來說, 比較的碼位. 所以在非 ascii 字符時, 得到的結(jié)果可能會不盡人意.
第五章: 一等函數(shù)
高階函數(shù)
高階函數(shù)就是接受函數(shù)作為參數(shù), 或者把函數(shù)作為返回結(jié)果的函數(shù). 如 map , filter , reduce等.
第六章: 使用一等函數(shù)實現(xiàn)設(shè)計模式
雖然設(shè)計模式與語言無關(guān), 但這并不意味著每一個模式都能在每一個語言中使用. Gamma 等人合著的 《設(shè)計模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》 一書中有 23 個模式, 其中有 16
個在動態(tài)語言中'不見了, 或者簡化了'.
這里不舉例設(shè)計模式, 因為書里的模式不常用.
第七章: 函數(shù)裝飾器和閉包
函數(shù)裝飾器用于在源碼中“標記”函數(shù),以某種方式增強函數(shù)的行為。這是一項強大的功 能,但是若想掌握,必須理解閉包。
修飾器和閉包經(jīng)常在一起討論, 因為修飾器就是閉包的一種形式. 閉包還是回調(diào)式異步編程和函數(shù)式編程風格的基礎(chǔ).
裝飾器基礎(chǔ)知識
裝飾器是可調(diào)用的對象, 其參數(shù)是另一個函數(shù)(被裝飾的函數(shù)). 裝飾器可能會處理被 裝飾的函數(shù), 然后把它返回, 或者將其替換成另一個函數(shù)或可調(diào)用對象.
閉包
閉包其實挺好理解的, 當匿名函數(shù)出現(xiàn)的時候, 才使得這部分難以掌握. 簡單簡短的解釋閉包就是:
名字空間與函數(shù)捆綁后的結(jié)果被稱為一個閉包(closure).
如果兩個變量都是指向同一個對象, 我們通常會說變量是另一個變量的 別名
.
在==和is之間選擇運算符 ==
是用來判斷兩個對象值是否相等(注意是對象值). 而 is
則是用于判斷兩個變量是否指向同一個對象, 或者說判斷變量是不是兩一個的別名, is 并不關(guān)心對象的值. 從使用上, ==
使用比較多, 而 is
的執(zhí)行速度比較快.
函數(shù)的參數(shù)做引用時
python中的函數(shù)參數(shù)都是采用共享傳參. 共享傳參指函數(shù)的各個形式參數(shù)獲得實參中各個引用的副本. 也就是說, 函數(shù)內(nèi)部的形參 是實參的別名.
這種方案就是當傳入?yún)?shù)是可變對象時, 在函數(shù)內(nèi)對參數(shù)的修改也就是對外部可變對象進行修改. 但這種參數(shù)試圖重新賦值為一個新的對象時則無效, 因為這只是相當于把參數(shù)作為另一個東西的引用, 原有的對象并不變. 也就是說, 在函數(shù)內(nèi), 參數(shù)是不能把一個對象替換成另一個對象的.
不要使用可變類型作為參數(shù)的默認值
參數(shù)默認值是個很棒的特性. 對于開發(fā)者來說, 應(yīng)該避免使用可變對象作為參數(shù)默認值. 因為如果參數(shù)默認值是可變對象, 而且修改了它的內(nèi)容, 那么后續(xù)的函數(shù)調(diào)用上都會收到影響.
del和垃圾回收
在python中, 當一個對象失去了最后一個引用時, 會當做垃圾, 然后被回收掉. 雖然python提供了 del 語句用來刪除變量. 但實際上只是刪除了變量和對象之間的引用, 并不一定能讓對象進行回收, 因為這個對象可能還存在其他引用.
在CPython中, 垃圾回收主要用的是引用計數(shù)的算法. 每個對象都會統(tǒng)計有多少引用指向自己. 當引用計數(shù)歸零時, 意味著這個對象沒有在使用, 對象就會被立即銷毀.
符合Python風格的對象
得益于 Python 數(shù)據(jù)模型,自定義類型的行為可以像內(nèi)置類型那樣自然。實現(xiàn)如此自然的 行為,靠的不是繼承,而是鴨子類型(duck typing):我們只需按照預(yù)定行為實現(xiàn)對象所 需的方法即可。
對象表示形式
每門面向?qū)ο蟮恼Z言至少都有一種獲取對象的字符串表示形式的標準方式。Python 提供了 兩種方式。
Python中的把使用一個下劃線前綴標記的屬性稱為'受保護的'屬性
使用slots類屬性節(jié)省空間
第十一章: 接口:從協(xié)議到抽象基類
這些協(xié)議定義為非正式的接口, 是讓編程語言實現(xiàn)多態(tài)的方式. 在python中, 沒有 interface
關(guān)鍵字, 而且除了抽象基類, 每個類都有接口: 所有類都可以自行實現(xiàn) __getitem__
和 __add__
.
有寫規(guī)定則是程序員在開發(fā)過程中慢慢總結(jié)出來的, 如受保護的屬性命名采用單個前導下劃線, 還有一些編碼規(guī)范之類的.
協(xié)議是接口, 但不是正式的, 這些規(guī)定并不是強制性的, 一個類可能只實現(xiàn)部分接口, 這是允許的.
既然有非正式的協(xié)議, 那么有沒有正式的協(xié)議呢? 有, 抽象基類就是一種強制性的協(xié)議.
抽象基類要求其子類需要實現(xiàn)定義的某個接口, 且抽象基類不能實例化.
Python文化中的接口和協(xié)議
引入抽象基類之前, python就已經(jīng)非常成功了, 即使現(xiàn)在也很少使用抽象基類. 通過鴨子類型和協(xié)議, 我們把協(xié)議定義為非正式接口, 是讓python實現(xiàn)多態(tài)的方式.
另一邊面, 不要覺得把公開數(shù)據(jù)屬性放入對象的接口中不妥, 如果需要, 總能實現(xiàn)讀值和設(shè)值方法, 把數(shù)據(jù)屬性變成特性. 對象公開方法的自己, 讓對象在系統(tǒng)中扮演特定的角色. 因此, 接口是實現(xiàn)特定角色的方法集合.
序列協(xié)議是python最基礎(chǔ)的協(xié)議之一, 即便對象只實現(xiàn)那個協(xié)議最基本的一部分, 解釋器也會負責地處理.
水禽和抽象基類
鴨子類型在很多情況下十分有用, 但是隨著發(fā)展, 通常由了更好的方式.
近代, 屬和種基本是根據(jù)表型系統(tǒng)學分類的, 鴨科屬于水禽, 而水禽還包括鵝, 鴻雁等. 水禽是對某一類表現(xiàn)一致進行的分類, 他們有一些統(tǒng)一'描述'部分.
因此, 根據(jù)分類的演化, 需要有個水禽類型, 只要 cls
是抽象基類, 即 cls
的元類是 abc.ABCMeta
, 就可以使用 isinstance(obj, cls)
來進行判斷.
與具類相比, 抽象基類有很多理論上的優(yōu)點, 被注冊的類必須滿足抽象基類對方法和簽名的要求, 更重要的是滿足底層語義契約.
標準庫中的抽象基類
大多數(shù)的標準庫的抽象基類在 collections.abc
模塊中定義. 少部分在 numbers
和 io
包中有一些抽象基類. 標準庫中有兩個 abc
模塊, 這里只討論 collections.abc
.
這個模塊中定義了 16 個抽象基類.
Iterable、Container 和 Sized各個集合應(yīng)該繼承這三個抽象基類,或者至少實現(xiàn)兼容的協(xié)議。 Iterable
通過 __iter__
方法支持迭代,Container 通過 __contains__
方法支持 in 運算符,Sized 通過 __len__
方法支持 len() 函數(shù)。
Sequence、Mapping 和 Set這三個是主要的不可變集合類型,而且各自都有可變的子類。
MappingView在 Python3 中,映射方法 .items()
、 .keys()
和 .values()
返回的對象分別是 ItemsView、KeysView 和 ValuesView 的實例。前兩個類還從 Set 類繼承了豐富的接 口。
Callable 和 Hashable這兩個抽象基類與集合沒有太大的關(guān)系,只不過因為 collections.abc
是標準庫中 定義抽象基類的第一個模塊,而它們又太重要了,因此才把它們放到 collections.abc
模塊中。我從未見過 Callable
或 Hashable
的子類。這兩個抽象基類的主要作用是為內(nèi) 置函數(shù) isinstance
提供支持,以一種安全的方式判斷對象能不能調(diào)用或散列。
Iterator注意它是 Iterable 的子類。
第十二章: 繼承的優(yōu)缺點
很多人覺得多重繼承得不償失, 那些不支持多繼承的編程語言好像也沒什么損失.
子類化內(nèi)置類型很麻煩
python2.2 以前, 內(nèi)置類型(如list, dict)是不能子類化的. 它們是不能被其他類所繼承的, 原因是內(nèi)置類型是C語言實現(xiàn)的, 不會調(diào)用用戶定義的類覆蓋的方法.
至于內(nèi)置類型的子類覆蓋的方法會不會隱式調(diào)用, CPython 官方也沒有制定規(guī)則. 基本上, 內(nèi)置類型的方法不會調(diào)用子類覆蓋的方法. 例如, dict 的子類覆蓋的 __getitem__
方法不會覆蓋內(nèi)置類型的 get()
方法調(diào)用.
多重繼承和方法解析順序
任何實現(xiàn)多重繼承的語言都要處理潛在的命名沖突,這種沖突由不相關(guān)的祖先類實現(xiàn)同名 方法引起。這種沖突稱為“菱形問題”,如圖.
Python 會按照特定的順序遍歷繼承 圖。這個順序叫方法解析順序(Method Resolution Order,MRO)。類都有一個名為 mro 的屬性,它的值是一個元組,按照方法解析順序列出各個超類,從當前類一直 向上,直到 object 類。
第十三章: 正確重載運算符
在python中, 大多數(shù)的運算符是可以重載的, 如 ==
對應(yīng)了 __eq__
, +
對應(yīng) __add__
.
某些運算符不能重載, 如 is, and, or, and
.
第十四章: 可迭代的對象、迭代器和生成器
迭代是數(shù)據(jù)處理的基石. 掃描內(nèi)存中放不下的數(shù)據(jù)集時, 我們要找到一種惰性獲取數(shù)據(jù)的方式, 即按需一次獲取一個數(shù)據(jù). 這就是 迭代器模式
.
python中有 yield
關(guān)鍵字, 用于構(gòu)建 生成器(generator)
, 其作用用于迭代器一樣.
所有的生成器都是迭代器, 因為生成器完全實現(xiàn)了迭代器的接口.
檢查對象 x 是否迭代, 最準確的方法是調(diào)用 iter(x)
, 如果不可迭代, 則拋出 TypeError
異常. 這個方法比 isinstance(x, abc.Iterable)
更準確, 因為它還考慮到遺留的 __getitem__
方法.
可迭代的對象與迭代器的對比
我們需要對可迭代的對象進行一下定義:
使用 iter 內(nèi)置函數(shù)可以獲取迭代器的對象。如果對象實現(xiàn)了能返回迭代器的 iter 方法,那么對象就是可迭代的。序列都可以迭代;實現(xiàn)了 getitem 方 法,而且其參數(shù)是從零開始的索引,這種對象也可以迭代。
我們要明確可迭代對象和迭代器之間的關(guān)系: 從可迭代的對象中獲取迭代器.
標準的迭代器接口有兩個方法:
__next__
: 返回下一個可用的元素, 如果沒有元素了, 拋出 StopIteration
異常.
__iter__
: 返回 self
, 以便咋應(yīng)該使用可迭代對象的地方使用迭代器.
典型的迭代器
為了清楚地說明可迭代對象與迭代器之間的重要區(qū)別, 我們將兩者分開, 寫成兩個類:
這個例子主要是為了區(qū)分可迭代對象和迭代器, 這種情況工作量一般比較大, 程序員也不愿這樣寫.
生成器表達式
生成器表達式可以理解為列表推導的惰性版本: 不會迫切地構(gòu)建列表, 而是返回一個生成器, 按需惰性生成元素. 也就是, 如果列表推導是產(chǎn)出列表的工廠, 那么生成器表達式就是產(chǎn)出生成器的工廠.
標準庫中的生成器函數(shù)
用于映射的生成器函數(shù)
模塊 | 函數(shù) | 說明 |
---|---|---|
itertools | accumulate(it, [func]) | 產(chǎn)出累積的總和;如果提供了 func,那么把前兩個元素傳給它,然后把計算結(jié)果和下一個元素傳給它,以此類推,最后產(chǎn)出結(jié)果 |
(內(nèi)置) | enumerate(iterable, start=0) | 產(chǎn)出由兩個元素組成的元組,結(jié)構(gòu)是 (index, item),其中 index 從 start 開始計數(shù),item 則從 iterable 中獲取 |
(內(nèi)置) | map(func, it1, [it2, ..., itN]) | 把 it 中的各個元素傳給func,產(chǎn)出結(jié)果;如果傳入 N 個可迭代的對象,那么 func 必須能接受 N 個參數(shù),而且要并行處理各個可迭代的對象 |
合并多個可迭代對象的生成器函數(shù)
模塊 | 函數(shù) | 說明 |
---|---|---|
itertools | chain(it1, ..., itN) | 先產(chǎn)出 it1 中的所有元素,然后產(chǎn)出 it2 中的所有元素,以此類推,無縫連接在一起 |
itertools | chain.from_iterable(it) | 產(chǎn)出 it 生成的各個可迭代對象中的元素,一個接一個,無縫連接在一起;it 應(yīng)該產(chǎn)出可迭代的元素,例如可迭代的對象列表 |
(內(nèi)置) | zip(it1, ..., itN) | 并行從輸入的各個可迭代對象中獲取元素,產(chǎn)出由 N 個元素組成的元組,只要有一個可迭代的對象到頭了,就默默地停止 |
新的句法:yield from
如果生成器函數(shù)需要產(chǎn)出另一個生成器生成的值, 傳統(tǒng)的方式是嵌套的 for 循環(huán), 例如, 我們要自己實現(xiàn) chain生成器:
可迭代的歸約函數(shù)
有些函數(shù)接受可迭代對象, 但僅返回單個結(jié)果, 這類函數(shù)叫規(guī)約函數(shù).
模塊 | 函數(shù) | 說明 |
---|---|---|
(內(nèi)置) | sum(it, start=0) | it 中所有元素的總和,如果提供可選的 start,會把它加上(計算浮點數(shù)的加法時,可以使用 math.fsum 函數(shù)提高精度) |
(內(nèi)置) | all(it) | it 中的所有元素都為真值時返回 True,否則返回 False;all([]) 返回 True |
(內(nèi)置) | any(it) | 只要 it 中有元素為真值就返回 True,否則返回 False;any([]) 返回 False |
(內(nèi)置) | max(it, [key=,] [default=]) | 返回 it 中值最大的元素;*key 是排序函數(shù),與 sorted 函數(shù)中的一樣;如果可迭代的對象為空,返回 default |
functools | reduce(func, it, [initial]) | 把前兩個元素傳給 func,然后把計算結(jié)果和第三個元素傳給 func,以此類推,返回最后的結(jié)果;如果提供了 initial,把它當作第一個元素傳入 |
第十五章: 上下文管理器和 else 塊
本章討論的是其他語言不常見的流程控制特性, 正因如此, python新手往往忽視或沒有充分使用這些特性. 下面討論的特性有:
with 語句和上下文管理器
for while try 語句的 else 子句
上下文管理器和with塊
使用@contextmanager
第十六章: 協(xié)程
預(yù)激協(xié)程的裝飾器
協(xié)程內(nèi)部如果不能處理這個異常, 就會導致協(xié)程終止.
第十七章: 使用期物處理并發(fā)
第十八章: 使用 asyncio 包處理并發(fā)
并發(fā)是指一次處理多件事。 并行是指一次做多件事。 二者不同,但是有聯(lián)系。 一個關(guān)于結(jié)構(gòu),一個關(guān)于執(zhí)行。 并發(fā)用于制定方案,用來解決可能(但未必)并行的問題?!?Rob Pike Go 語言的創(chuàng)造者之一
并行是指兩個或者多個事件在同一時刻發(fā)生, 而并發(fā)是指兩個或多個事件在同一時間間隔發(fā)生. 真正運行并行需要多個核心, 現(xiàn)在筆記本一般有 4 個 CPU 核心, 但是通常就有超過 100 個進程同時運行. 因此, 實際上大多數(shù)進程都是并發(fā)處理的, 而不是并行處理. 計算機始終運行著 100 多個進程, 確保每個進程都有機會取得發(fā)展, 不過 CPU 本身同時做的事情不會超過四件.
從期物、任務(wù)和協(xié)程中產(chǎn)出
避免阻塞型調(diào)用
有兩種方法能避免阻塞型調(diào)用中止整個應(yīng)用程序的進程:
在單獨的線程中運行各個阻塞型操作
把每個阻塞型操作轉(zhuǎn)換成非阻塞的異步調(diào)用使用
多線程是可以的, 但是會消耗比較大的內(nèi)存. 為了降低內(nèi)存的消耗, 通常使用回調(diào)來實現(xiàn)異步調(diào)用. 這是一種底層概念, 類似所有并發(fā)機制中最古老最原始的那種--硬件中斷. 使用回調(diào)時, 我們不等待響應(yīng), 而是注冊一個函數(shù), 在發(fā)生某件事時調(diào)用. 這樣, 所有的調(diào)用都是非阻塞的.
異步應(yīng)用程序底層的事件循環(huán)能依靠基礎(chǔ)設(shè)置的中斷, 線程, 輪詢和后臺進程等待等, 確保多個并發(fā)請求能取得進展并最終完成, 這樣才能使用回調(diào). 事件循環(huán)獲得響應(yīng)后, 會回過頭來調(diào)用我們指定的回調(diào). 如果做法正確, 事件循環(huán)和應(yīng)用代碼公共的主線程絕不會阻塞.
把生成器當做協(xié)程使用是異步編程的另一種方式. 對事件循環(huán)來說, 調(diào)用回調(diào)與在暫停的協(xié)程上調(diào)用 .send()效果差不多.
覆蓋型與非覆蓋型描述符對比
python存取屬性的方式是不對等的. 通過實例讀取屬性時, 通常返回的是實例中定義的屬性, 但是, 如果實例中沒有指定的屬性, 那么會從獲取類屬性. 而實例中屬性賦值時, 通常會在實例中創(chuàng)建屬性, 根本不影響類.
覆蓋型描述符
我們要做一個在運行時創(chuàng)建類的, 類工廠函數(shù):
元類基礎(chǔ)知識
元類是制造類的工廠, 不過不是函數(shù), 本身也是類. 元類是用于構(gòu)建類的類 .
謝謝大家閱讀,謝謝“棲遲於一丘”的分享!超牛逼!原文鏈接