python 数据模型
點擊上方“算法猿的成長“,關注公眾號,選擇加“星標“或“置頂”
總第 129?篇文章,本文大約??4500?字,閱讀大約需要 15?分鐘
最近開始閱讀《流暢的python》,也會開始更新這本書的學習筆記
第一篇的內容是第一章 python 數據模型,主要是介紹 python 類中的特殊方法(或者說魔術方法),這類特殊方法的實現可以讓我們自定義的類對象能夠使用 python 標準庫的方法,同時也有助于接口方法的一致性。
本文的代碼例子:
https://github.com/ccc013/CodesNotes/blob/master/FluentPython/1_Python%E6%95%B0%E6%8D%AE%E6%A8%A1%E5%9E%8B.ipynb
前言
數據模型其實是對 Python 框架的描述,它規范了這門語言自身構建模塊的接口,這些模塊包括但不限于序列、迭代器、函數、類和上下文管理器。
通常在不同框架下寫程序,都需要花時間來實現那些會被框架調用的方法,python 當然也包含這些方法,當 python 解釋器碰到特殊的句法的時候,會使用特殊方法來激活一些基本的對象操作,這種特殊方法,也叫做魔術方法(magic method),通常以兩個下劃線開頭和結尾,比如最常見的 __init__, __len__ 以及 __getitem__ 等,而 obj[key] 這樣的操作背后的特殊方法是 __getitem__,初始化一個類示例的時候,如 obj= Obj() 的操作背后,特殊方法就是 __init__。
通過實現 python 的這些特殊方法,可以讓自定義的對象實現和支持下面的操作:
迭代
集合類
屬性訪問
運算符重載
函數和方法的調用
對象的創建和銷毀
字符串表示形式和格式化
管理上下文(也就是 with 塊)
一摞 Python 風格的紙牌
接下來嘗試自定義一個類,并實現兩個特殊方法:__getitem__ 和 __len__ ,看看實現它們后,可以對自定義的類示例實現哪些操作。
這里自定義一個紙牌類,并定義了數字和花色,代碼如下所示:
import?collections #?用?nametuple?構建一個類來表示紙牌 Card?=?collections.namedtuple('Card',?['rank',?'suit'])class?FrenchDeck:ranks?=?[str(n)?for?n?in?range(2,?11)]?+?list('JQKA')suits?=?'spades?diamonds?clubs?hearts'.split()def?__init__(self):self._cards?=?[Card(rank,?suit)?for?suit?in?self.suits?for?rank?in?self.ranks]def?__len__(self):return?len(self._cards)def?__getitem__(self,?position):return?self._cards[position]其中輔助用到 collections 庫的 nametuple ,用來表示一張紙牌,其屬性包括數字 rank 和 花色 suit ,下面是對這個 Card 的簡單測試:
#?測試?Card beer_card?=?Card('7',?'diamonds') beer_card接著就是測試自定義的 FrenchDeck 類,這里會調用 len() 方法看看一摞紙牌有多少張:
#?測試?FrenchDeck deck?=?FrenchDeck() len(deck)然后是進行索引訪問的操作,這里測試從正序訪問第一張,以及最后一張紙牌的操作:
print(deck[0],?deck[-1])如果想進行隨機抽取卡牌,可以結合 random.choice 來實現:
#?隨機抽取,結合?random.choice from?random?import?choicechoice(deck)由于我們實現 __getitem__ ?方法是獲取紙牌,所以也可以支持切片(slicing)的操作,例子如下所示:
#?切片 print(deck[:3]) print(deck[12::13])另外,實現 __getitem__ 方法就可以支持迭代操作:
#?可迭代的讀取 for?card?in?deck:print(card)反向迭代也自然可以做到:
#?反向迭代 for?card?in?reversed(deck):print(card)break另外,當然也可以自定義排序規則,如下所示:
#?制定排序的規則 suit_values?=?dict(spades=3,?hearts=2,?diamonds=1,?clubs=0)def?spades_high(card):rank_value?=?FrenchDeck.ranks.index(card.rank)return?rank_value?*?len(suit_values)?+?suit_values[card.suit]#?對卡牌進行升序排序 for?card?in?sorted(deck,?key=spades_high):print(card)總結一下,實現 python 的特殊方法的好處包括:
統一方面的名稱,如果有別人采用你自定義的類,不用花更多精力記住不同的名稱,比如獲取數量都是 len() 方法,而不會是 size 或者 length
可以更加方便利用 python 的各種標準庫,比如 random.choice 、reversed、sorted ,不需要自己重新發明輪子
如何使用特殊方法
這里分兩種情況來說明對于特殊方法的調用:
python 內置的類型:比如列表(list)、字典(dict)等,那么 CPython 會抄近路,即 __len__ 實際上會直接返回 PyVarObject 里的 ob_size 屬性。PyVarObject 是表示內存中長度可變的內置對象的 C 語言結構體,直接讀取這個值比調用一個方法要快很多。
自定義的類:通過內置函數(如 len, iter, str 等)調用特殊方法是最好的選擇。
對于特殊方法的調用,這里還要補充說明幾點:
特殊方法的存在是為了被 Python 解釋器調用的。我們不需要調用它們,即不需要這么寫 my_object.__len__(),而應該是 len(my_object),這里的 my_object 表示一個自定義類的對象。
通常對于特殊方法的調用都是隱式的。比如 for i in x 循環語句是用 iter(x) ,也就是調用 x.__iter__() ?方法。
除非有大量元編程存在,否則都不需要直接使用特殊方法;
接下來是實現一個自定義的二維向量類,然后自定義加號的特殊方法,實現運算符重載。
代碼例子如下所示:
#?一個簡單的二維向量類 from?math?import?hypotclass?Vector:def?__init__(self,?x=0,?y=0):self.x?=?xself.y?=?ydef?__repr__(self):return?'Vector(%r,?%r)'?%?(self.x,?self.y)def?__abs__(self):return?hypot(self.x,?self.y)def?__bool__(self):return?bool(abs(self))def?__add__(self,?other):x?=?self.x?+?other.xy?=?self.y?+?other.yreturn?Vector(x,?y)def?__mul__(self,?scalar):return?Vector(self.x?*?scalar,?self.y?*?scalar)這里除了必須實現的 __init__外,還實現了幾個特殊方法:
__add__: 加法運算符;
__bool__ :用于判斷是否真假,也就是在調用bool() 方法;默認情況下是自定義類的實例總是被認為是真的,但如果實現了 __bool__或者 __len__ ,則會返回它們的結果,bool()首先嘗試返回 __bool__ 方法,如果沒有實現,則會嘗試調用 __len__ 方法
__mul__ :實現的是標量乘法,即向量和數的乘法;
__abs__ :如果輸入是整數或者浮點數,返回輸入值的絕對值;如果輸入的是復數,返回這個復數的模;如果是輸入向量,返回的是它的模;
__repr__ : 可以將對象用字符串的形式表達出來;
這里要簡單介紹下 __repr__ 和 __str__ 兩個方法的區別:
__repr__ :交互式控制臺、調試程序(debugger)、% 和 str.format 方法都會調用這個方法來獲取字符串形式;
__str__ :主要是在 str() 和 print() 方法中會調用該方法,它返回的字符串會對終端用戶更加友好;
如果只想實現其中一個方法,__repr__ ?是更好的選擇,因為默認會調用 __repr__ 方法。
接下來就是簡單測試這個類,測試結果如下所示:
特殊方法一覽
下面分別根據是否和運算符相關分為兩類的特殊方法:
和運算符無關的特殊方法
| 字符串/字節序列表現形式 | __repr__, __str__,__format__,__bytes__ |
| 數值轉換 | __abs__,__bool__,__complex__,__int__,__float__,__hash__,__index__ |
| 集合模擬 | __len__,__getitem__,__setitem__,__delitem__,__contains__ |
| 迭代枚舉 | __iter__,__reversed__,__next__ |
| 可調用模擬 | __call__ |
| 上下文管理 | __enter__, __exit__ |
| 實例創建和銷毀 | __new__,__init__,__del__ |
| 屬性管理 | __getattr__,__getattribute__,__setattr__,__delattr__,__dir__ |
| 屬性描述符 | __get__,__set__,__delete__ |
| 跟類相關的服務 | __prepare__,__instancecheck__,__subclasscheck__ |
和運算符相關的特殊方法
| 一元運算符 | __neg__ -, __pos__ +,__abs__ abs() |
| 眾多比較運算符 | __lt__ <, __le__ <=, __eq__ ==, __ne__ !=, __gt__ >, __ge__ >= |
| 算術運算符 | __add__ +, __sub__ -, __mul__ *, __truediv__ /, __floordiv__ //, __mod__ %, __divmod__ divmod(), __pow__ **或者pow(), __round__ round() |
| 反向算法運算符 | __radd__, __rsub__, __rmul__, __rtruediv__, __rfloordiv__, __rmod__, __rdivmod__, __rpow__ |
| 增量賦值算術運算符 | __iadd__, __isub__, __imul__, __itruediv__, __ifloordiv__, __imod__, __ipow__ |
| 位運算符 | __invert__ ~, __lshift__ <<, __rshift__ >>, __and__ &, __or__ |, __xor__ ^ |
| 反向位運算符 | __rlshift__, __rrshift__, __rand__, __rxor__, __ror__ |
| 增量賦值位運算符 | __ilshift__, __irshift__, __iand__, __ixor__, __ior__ |
這里有兩類運算符要解釋一下:
反向運算符:交換兩個操作數的位置的時候會調用反向運算符,比如 b * a 而不是 a * b ;
增量賦值運算符:把一種中綴運算符變成賦值運算的捷徑,即是 a *= b 的操作
為什么 len 不是普通方法
len 之所以不是普通方法,是為了讓 Python 自帶的數據結構變得高效,前面也提到內置類型在使用 len 方法的時候,CPython 會直接從一個 C 結構體里讀取對象的長度,完全不會調用任何方法,因此速度會非??臁6?python 的內置類型,比如列表 list、字符串 str、字典 dict 等查詢數量是非常常見的操作。
這種處理方式實際上是在保持內置類型的效率和保證語言的一致性之間找到一個平衡點。
小結
本文介紹了兩個代碼例子,說明了在自定義類的時候,實現特殊方法,可以實現和內置類型(比如列表、字典、字符串等)一樣的操作,包括實現迭代、運算符重載、打印類實例對象等,然后還根據是否和運算符相關將特殊方法分為兩類,并列舉出來了,最后也介紹了 len 方法的例子來說明 python 團隊是如何保持內置類型的效率和保證語言一致性的。
精選AI文章
1.?10個實用的機器學習建議
2.?深度學習算法簡要綜述(上)
3.?深度學習算法簡要綜述(上)
4.?常見的數據增強項目和論文介紹
5.?實戰|手把手教你訓練一個基于Keras的多標簽圖像分類器
精選python文章
1.?Python 基礎入門--簡介和環境配置
2.?python 學習資源推薦
3.?快速入門 Jupyter notebook
4.?Jupyter 進階教程
5.?10個高效的pandas技巧
精選教程資源文章
1.?[資源分享] TensorFlow 官方中文版教程來了
2.?[資源]推薦一些Python書籍和教程,入門和進階的都有!
3.?[Github項目推薦] 推薦三個助你更好利用Github的工具
4.?Github上的各大高校資料以及國外公開課視頻
5.?GitHub上有哪些比較好的計算機視覺/機器視覺的項目?
歡迎關注我的微信公眾號--算法猿的成長,或者掃描下方的二維碼,大家一起交流,學習和進步!
?如果覺得不錯,在看、轉發就是對小編的一個支持!
總結
以上是生活随笔為你收集整理的python 数据模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 挥泪推荐6款非常非常非常实用的软件!
- 下一篇: Python读取PDF文档并翻译