Python學習之路20-數據模型

《流暢的Python》筆記。
本篇是Python進階篇的開始。本篇主要是對Python特殊方法的概述。

1. 前言

數據模型其實是對Python框架的描述,它規范了這門語言自身構件模塊的接口,這些模塊包括但不限于序列、迭代器、函數、類和上下文管理器。不管在哪種框架下寫程序,都會花費大量時間去實現那些會被框架本身調用的方法,Python也不例外。Python解釋器碰到特殊句法時,會使用特殊方法去激活一些基本的對象操作,這些特殊方法的名字以兩個下劃線開頭,以兩個下劃線結尾(所以特殊方法也叫雙下方法 dunder method),這些特殊方法名能讓自己編寫的對象實現和支持以下的語言構架,并與之交互:

迭代、集合類、屬性訪問、運算符重載、函數和方法的調用、對象的創建和銷毀、字符串表示形式和格式化、管理上下文(即with塊)。

下面通過一些例子來介紹常用的特殊方法。

2. Python風格紙牌

首先介紹兩個特殊方法__getitem____len__這兩個特殊方法。以下代碼創建了一個紙牌類:

import collectionsCard = 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]

namedtuple,即命名元組,類似于C/C++中的struct,定義如下:

collections.namedtuple(typename, field_names, verbose=False, rename=False)

第一個參數是元組名;第二個是該元組中含的屬性名;第三個參數表示在構建該命名元組之前先打印出該命名元組的結構,如果在控制臺輸入第3行代碼,并置verboseTrue的話,會輸出該命名元組的內部結構,實際上它是一個繼承自tuple的類,由于輸出過長,請大家自行實驗;如果該命名元組的元素名中有Python關鍵字,則需要置第四個參數為True,這些與關鍵字重名的元素名會被特殊處理。

用命名元組創建一個不帶方法的對象十分簡單:

>>> from chapter20 import Card, FrenchDeck
>>> beer_card = Card("7", "diamonds")
>>> beer_card
Card(rank='7', suit='diamonds')

由于FrenchDeck實現了__getitem__方法,所以可以像操作ListTuple一樣操作FrenchDeck,比如隨機訪問,切片:

>>> deck = FrenchDeck()
>>> len(deck)
52
>>> deck[0]
Card(rank='2', suit='spades')
>>> deck[-1]
Card(rank='A', suit='hearts')
>>> from random import choice
>>> choice(deck)
Card(rank='4', suit='clubs')
>>> choice(deck)
Card(rank='J', suit='clubs')
>>> deck[:3]
[Card(rank='2', suit='spades'), Card(rank='3', suit='spades'), Card(rank='4', suit='spades')]
>>> deck[12::13]
[Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), 
Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]

由于實現了該方法,FrenchDeck還是個可迭代對象,即可以用for循環對其訪問(也可以反向訪問reversed):

>>> for card in deck:
>>> ... print(card)Card(rank='2', suit='spades')
Card(rank='3', suit='spades')
Card(rank='4', suit='spades')
-- snip --
Card(rank='Q', suit='hearts')
Card(rank='K', suit='hearts')
Card(rank='A', suit='hearts')

迭代通常是隱式的,譬如說一個集合類型沒有實現__contains__方法,那么in運算符就會按順序做一次迭代搜索(調用__getitem__),于是in運算符可以用在FrenchDeck上:

>>> Card('2', 'spades') in deck
True

如果對上述deck變量調用sorted函數,Python將按ASCII碼進行排序,但這并不是撲克牌的正確排序,所以下面我們自定義排序方法:

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)

此時輸出的結果就是先按點數排序,再按花色排序。

3. 如何使用特殊方法

需要明確一點,特殊方法的存在是為了給Python解釋器調用到,作為程序員并不需要調用他們,也即是說,沒有my_object.__len__()這種寫法,而應該是len(my_object)。說到__len__方法,如果是Python內置類型,CPython會抄個近路,該方法實際上會直接返回PyVarObject里的ob_size屬性,而PyVarObject是表示內存中長度可變的內痔對象的C語言結構體。

很多時候特殊方法的調用是隱式的,比如for i in x:這個語句,背后其實用的是iter(x),而這個函數的背后則是x.__iter__()方法,當然前提是這個方法在x中被實現(如果沒被實現則會調用__getitem__方法)。

直接調用這個值比調用一個方法快很多。直接調用特殊方法的頻率應該遠遠低于你去實現它們的次數。

通過內置的函數(例如leniterstr等)來使用特殊方法是最好的選擇。這些內置函數不僅會調用特殊方法,通常還提供額外的好處,而且對于內置的類來說,它們的速度更快。

還有一點值得注意:不要想當然地隨意添加特殊方法,比如__foo__之類的,因為雖然現在這個名字沒有被Python內部使用,以后就不一定了。

3.1 自定義向量Vector

使用5個特殊方法實現Vector的字符串輸出,取絕對值(如果是復數則是取模),返回布爾值,加法和數乘等運算:

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)# 在Python中,只有0,NULL才是False,其余均為Truedef __bool__(self):# 更簡單的寫法是:# return bool(self.x or self.y)return bool(abs(self))# 實現加法運算符重載def __add__(self, other):return Vector(self.x + other.x, self.y + other.y)# 實現乘法運算符重載,這里是數乘,且還沒有實現交換律(需要實現__rmul__方法)def __mul__(self, scalar):return Vector(self.x * scalar, self.y * scalar)

Python有一個內置函數叫做repr。該函數通過特殊方法__repr__來得到一個對象的字符串表示形式,如果沒有該特殊方法,當我們在控制臺打印一個向量對象時,得到的字符串可能是<Vector object at 0x10e00070>

# 代碼:
v1 = Vector(2, 4)
v2 = Vector(2, 1)
print(v1 + v2)
print(abs(v1))
print(v1 * 3)# 結果:
Vector(4, 5)
4.47213595499958
Vector(6, 12)

__repr____str__的區別與聯系:前者方便我們調試和記錄日志,后者則是給終端用戶看的。后者是在str()函數被使用,或者是在print函數打印一個對象的時候才被調用,它返回的字符串對終端用戶友好。如果只想實現這兩個特殊方法中的一個,__repr__是更好的選擇,因為如果一個對象沒有__str__函數,Python又需要調用它時,解釋器會用__repr__代替。

上述Vector類實現了__bool__方法,它可用于需要布爾值的上下文中(if, while, and, or, not等)。默認情況下,我們自己定義的類的實例總被認為是True,除非重寫了這個類的__bool____len__方法。bool(x)的背后是調用x.__bool__();如果不存在__bool__方法,那么bool(x)會嘗試調用x.__len__(),如果該方法返回0,則bool返回False,否則返回True

3.2 為什么len不是普通方法

“實用勝于純粹”(Python之禪里的一句話)。len之所以不是一個普通方法,是為了讓Python自帶的數據結構可以走后門,abs也是同理。但多虧了它是特殊方法,我們也可以把len用于自定義數據類型。這種處理方式在保持內置類型的效率和保證語言的一致性之間找到了一個平衡點,也印證了“Python之禪”中的另一句話:“不能讓特例特殊到考試破壞既定規則”。

4. 總結

通過實現特殊方法,自定義數據類型可以表現得跟內置類型一樣,從而讓我們寫出更具Python風格(Pythonic)的代碼。后面的內容將圍繞更多的特殊方法展開。


迎大家關注我的微信公眾號"代碼港" & 個人網站 www.vpointer.net ~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/451643.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/451643.shtml
英文地址,請注明出處:http://en.pswp.cn/news/451643.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

String s1==s2面試題

這個程序運行的結果是什么&#xff1f;class StringTest1{public static void main(String[] args) {String s1"equal";String s2 "equal";if(s1s2){System.out.println("s1s2");}else{ System.out.println("s1!s2");} }} 輸出 s1…

Vector:動態數組的使用和說明

摘自百度百科&#xff1a; 1. Vector 類在 java 中可以實現自動增長的對象數組; 創建了一個向量類的對象后&#xff0c;可以往其中隨意地插入不同的類的對象&#xff0c;既不需顧及類型也不需預先選定向量的容量&#xff0c;并可方便地進行查找。對于預先不知或不愿預先定義數組…

Spring AOP 代理模式

記錄幾篇關于AOP & 代理模式 的博客&#xff0c;寫的非常好&#xff0c;感謝作者分享&#xff01; Java中的代理模式——靜態代理以及分析靜態代理的缺點 Java中動態代理的兩種方式JDK動態代理和cglib動態代理以及區別 Spring中的AOP以及切入點表達式和各種通知 Spring…

mongodb 多表查詢

今天有一個業務涉及到mongodb的多表查詢&#xff0c;大體記錄下語句結構 db.table_a.aggregate([ {$lookup:{from:"table_b",localField:"userid",foreignField:"userid",as:"organization_doc"}}, //聯表B{ $project:{ _id:1, card…

跨平臺多線程編程

多線程介紹POSIX 1003.1-2001 定義了多線程編程的標準API。這個API就是廣為人知的pthreads。它的目的在于為跨平臺編寫多線程程序提供便利。多線程程序的編寫本文介紹了Linux 和 WIN32 平臺下的多線程程序的編寫方法Linux 系統對 pthreads 提供了良好的支持。一般地安裝完Linux…

方法重載和方法重寫

方法重載 &#xff08;1&#xff09; 方法重載是讓類以統一的方式處理不同類型數據的一種手段。多個同名函數同時存在&#xff0c;具有不同的參數個數/類型。 重載Overloading是一個類中多態性的一種表現。 &#xff08;2&#xff09; Java的方法重載&#xff0c;就是在類中可以…

shell獲取/etc/passwd中的用戶名和id

核心思想&#xff1a;cut 關鍵詞&#xff1a;head tail cut #!/bin/bash # get the information about /etc/passwd PATH/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATHfile"/etc/passwd" linewc -l $file | cut -d" "…

進度條控制(Windows 公共進度欄控件的功能)

MSDN&#xff1a;https://msdn.microsoft.com/zh-cn/library/sys15k39.aspx 構造 CProgressCtrl 對象&#xff1a;  CProgressCtrl(); 創建進度欄控件&#xff1a;  virtual BOOL Create( DWORD dwStyle(風格), const RECT& rect(位置大小), CWnd* pParentWnd(父窗體),…

本地瀏覽器緩存sessionStorage(臨時存儲) localStorage(長期存儲)的使用

對瀏覽器來說&#xff0c;使用 Web Storage 存儲鍵值對比存儲 Cookie 方式更直觀&#xff0c;而且容量更大&#xff0c;它包含兩種&#xff1a;localStorage 和 sessionStorage sessionStorage&#xff08;臨時存儲&#xff09; &#xff1a;為每一個數據源維持一個存儲區域&am…

HTML5的歷史

HTML5和WHATWG[1] 2004年Opera的IanHickson&#xff08;現在Google工作&#xff09;發起的HTML新標簽擴展以適應新的web應用&#xff0c;該發起遭到W3C的拒絕&#xff0c;而W3C憑空杜撰的作風引起了部分人的不滿&#xff0c;來自Opera,Apple&#xff0c;以及Mozilla的代表開始…

B-TREE、B+TREE、數據庫索引

推薦文章&#xff1a; B-Tree詳解 SQL夯實基礎&#xff08;五&#xff09;&#xff1a;索引的數據結構 MySQL B樹索引和哈希索引的區別 感謝作者分享&#xff01;

《重構-改善既有代碼的設計》-第1例:租賃影片(1)

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 買了《重構 - 改善既有代碼的設計 》一書&#xff0c;一直沒有好好看&#xff0c;大致過了下也覺得只是有點點印象而已&#xff0c;最后…

java類加public和不加public

加public表示全局類&#xff0c;該類可以import到任何類內。不加public默認為保留類&#xff0c;只能被同一個包內的其他類引用。

STL數據結構

STL數據結構 1.priority_queue #include<queue> pritority<int>q;(大根堆) priority_queue<int,vector<int>,greater<int> >q;(小根堆) struct no{ int x,v; bool operator <(const no &T)const{return v>T.v;} // v值xiao的優先 }; q…

《netty實戰》閱讀筆記(2)——Netty 的數據容器ByteBuf

ByteBuffer 當我們進行數據傳輸的時候&#xff0c;往往需要使用到緩沖區&#xff0c;常用的緩沖區就是JDK NIO類庫提供的java.nio.Buffer。 實際上&#xff0c;7種基礎類型&#xff08;Boolean除外&#xff09;都有自己的緩沖區實現&#xff0c;對于NIO編程而言&#xff0c;我們…

JAVA HASHMAP 用法

import java.util.HashMap;public class Student { String name; String sex; public Student(String n,String s) { namen; sexs; } public String toString(){ return ("姓名:"name"\n""性別:"sex"\n…

有了二叉查找樹、平衡樹為啥還需要紅黑樹?

參考文章&#xff1a; 有了二叉查找樹、平衡樹為啥還需要紅黑樹&#xff1f; 漫畫AVL樹 謝謝作者分享&#xff01;

32位Windows7上8G內存使用感受+xp 32位下使用8G內存

我推薦做開發的朋友:趕快加入8G的行列吧....呵呵..超爽...速度超快...基本沒有等待的概念...深有體會... 為什么要使用8G內存&#xff1f;在國內外各大論壇上&#xff0c;這都是一個有爭議的問題。問題的反方論據非常充分&#xff1a; 除了少數專業領域&#xff0c;大多數應用程…

《重構-改善既有代碼的設計》-第1例:租賃影片(2)

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 上接 重構-改善既有代碼的設計-第1例&#xff1a;租賃影片&#xff08;1&#xff09; 2 運用多態取代與價格相關的條件邏輯 2.1 最好不…

elasticsearch 的查詢 /_nodes/stats 各字段意思

/_nodes/stats 字段意思 “” 1 { 2 "_nodes": {3 "total": 1,4 "successful": 1,5 "failed": 06 },7 "cluster_name": "ELKTEST",8 "nodes": {9 "lnlHC8yERCKXCuAc…