日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

《流畅的Python第二版》读书笔记——Python数据模型

發布時間:2023/12/20 python 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《流畅的Python第二版》读书笔记——Python数据模型 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

引言

這是《流暢的Python第二版》搶先版的讀書筆記。Python版本暫時用的是python-3.8。為了使開發更簡單、快捷,本文使用了JupyterLab。

Python解釋器調用一些特殊方法來進行基本的對象操作,這種特殊方法通常有特殊的寫法。比如__getitem__,如果你實現了該方法,就可以通過obj[key]來觸發obj.__getitem__(key)方法。

這些特殊方法名能讓你自己的對象實現和支持以下的語言構架,并與之交互:

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

有些人也稱這些特殊方法為魔術方法(magic method)。

A Pythonic Card Deck

本節這個例子雖然簡單,但是它通過實現__getitem__和__len__方法來展示特殊方法的強大。

frenchdeck.py:

import collections# 利用namedtuple構造一個簡單的表示紙牌的類 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.suitsfor rank in self.ranks]def __len__(self):return len(self._cards)def __getitem__(self, position):return self._cards[position]

我們可以像下面這樣很方便的構造Card對象:

from frenchdeck import Card,FrenchDeckbeer_card = Card('7', 'diamonds') beer_card


但本例的重點是FrenchDeck類(一疊紙牌),它雖然很小,但很強大。像標準的Python集合一樣,可以調用len()函數來獲取對象中Card實例的數量:

deck = FrenchDeck() len(deck)


還可以從一疊紙牌中抽取第一張和最后一張,很簡單deck[0]、deck[-1]即可,這是__getitem__方法提供的:

Python提供了一個函數從序列中隨機獲取元素:random.choice,我們可以直接用在deck實例上:

from random import choice choice(deck)


現在我們已經看到了實現特殊方法來利用Python數據模型的兩個好處:

  • 作為你的類的用戶,無需記憶標準操作的各種名稱(比如如何獲取元素個數,通過.size()還是.length())。
  • 更加方便地利用Python標準庫,避免重復造輪子。就像random.choice函數一樣。

因為__getitem__方法把[]操作委托(delegates)給了self._cards列表,我們的deck自動支持切片。下面展示了如何查看牌堆中前三張牌,然后通過從第13張牌開始(索引是12),每隔13張牌抽一張牌,來選取牌A:

deck[:3] deck[12::13]


而且,僅僅是實現了__getitem__特殊方法,我們的deck還可以迭代:

for card in deck:print(card)

(上圖只截取部分)
同時也可以反向迭代:

for card in reversed(deck):print(card)


迭代通常是隱式的,如果一個集合沒有實現__contains__方法,那么in運算符就會執行一個順序掃描。
于是,in 運算符可以用在我們的FrenchDeck 類上,因為它是可迭代的:

Card('Q', 'hearts') in deck Card('7','beasts') in deck


還可以實現排序,比如用點數來判斷紙牌大小,2最小、A最大;同時黑桃(spades)最大、紅桃(hearts)次之、方塊(diamonds)再次、梅花(clubs)最小。下面就是按照這個規則來排序的函數,約定梅花2的大小是0,黑桃A是51:

suit_values = dict(spades=3, hearts=2, diamonds=1,clubs=0) def spades_high(card):rank_value = FrenchDeck.ranks.index(card.rank)# 點數乘以4 + 花色值return rank_value * len(suit_values) + suit_values[card.suit]

有了這個函數,就你可以對牌堆進行升序排序了:

for card in sorted(deck, key=spades_high):print(card)


通過實現__len__和__getitem__這兩個特殊方法,FrenchDeck就跟一個Python自有的序列數據類型一樣,可以體現出Python的核心語言特性(例如迭代和切片)。同時這個類還可以用于標準庫中random.choice、reversed和sorted這些函數。另外,對于組合的運用是的__len__和__getitem__的具體實現可以委托給self._cards這個list對象。

如何使用特殊方法

首先要知道,特殊方法是被Python解釋器而不是你調用的,你不能寫my_object.__len__(),而是寫len(my_object):Python會調用你實現的__len__方法。

然后如果是Python內置類型,比如列表、字符串、字節序列(bytearray)等,那么解釋器會走捷徑,__len__實際上會直接返回PyVarObject里的ob_size屬性,直接讀取這個值比調用一個方法要快很多。

很多時候,特殊方法調用是隱式的。比如語句for i in x:實際上會導致調用iter(x),而背后解釋器會調用x.__iter__(),如果有的這個方法的話。否則像FrenchDeck例子一樣,調用x.__getitem__()。

通常你無需直接調用特殊方法,除非有大量的元編程存在。唯一的例外可能是__init__方法。

通過內置函數來使用特殊方法是最好的選擇。

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

模擬數值類型

利用特殊方法,可以讓自定義對象通過加號+等運算符進行運算。
我們實現一個二維向量類vector,就是我們數學中的向量。

上圖是一個向量加法的例子:Vector(2,4)+Vector(2,1) = Vector(4,5)

我們實現的向量類應該能做這樣的加法,通過+運算符:

>>> v1 = Vector(2,4) >>> v2 = Vector(2,1) >>> v1 + v2 Vector(4,5)

其中+運算符得到的結果也是一個向量,并且打印出來的結果很友好。

我們的向量也應該支持abs函數,返回的是向量的模·:

>>> v = Vector(3,4) >>> abs(v) # sqrt(3**2+4**2) 5.0

還可以利用*運算符實現向量的標量乘法(向量與數的乘法):

>>> v * 3 Vector(9,12) >>>abs(v * 3) 15.0

下面就來實現這樣一個Vector類,上面提到的操作在代碼中是用__repr__、__abs__、__add__和__mul__實現的:

""" vector2d.py: 一個展示一些特殊方法的簡單類Addition::>>> v1 = Vector(2, 4)>>> v2 = Vector(2, 1)>>> v1 + v2Vector(4, 5)Absolute value::>>> v = Vector(3, 4)>>> abs(v)5.0Scalar multiplication::>>> v * 3Vector(9, 12)>>> abs(v * 3)15.0"""import mathclass Vector:def __init__(self, x=0, y=0):self.x = xself.y = ydef __repr__(self):return f'Vector({self.x!r}, {self.y!r})'def __abs__(self):return math.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)

乍一看我們實現了6個特殊方法,包括__init__初始化方法。下面來介紹其他幾個方法。

字符串表示

__repr__特殊方法被Python內置的repr函數調用,來展示一個對象的字符串形式。類似于Java中的toString()。

交互式控制臺和調試程序(debugger)用repr函數來獲取字符串表示形式。傳統的字符串格式中使用%運算符,現在有一種新的字符串格式化語法!r用在str.format方法中(也可以在要格式的字符串前加個f,就如上面代碼所示的)。也是利用repr把!r替換為字符串。

一些例子:

"Harold's a clever {0!s}" # 調用 str() , s for str "Bring out the holy {name!r}" # 調用repr(),r for repr

注意在我們的__repr__實現中,使用了!r來獲取對象各個屬性的標準字符串表示。這是一個好習慣,它展示了Vector(1,2)和Vector('1','2')的不同。后者會在我們的定義中報錯,因為構造函數只接受數值,而不是字符串。

其實作者在第一版中說使用%r是一個好習慣。

__repr__所返回的字符串應該準確且無歧義,并盡可能表達出如何用代碼創建出這個被打印的對象。因此這里使用了類似調用對象構造函數的表達形式。

__repr__和__str__的區別在于,后者在str()函數被使用時調用,或者通過print函數隱式的調用。

如果你只想實現這兩個方法中的一個,那么__repr__是更好的選擇。因為當沒有自定義的__str__,__str__方法默認會調用__repr__。

算術運算符

上面的代碼中實現了兩個運算:+和*,用到了__add__和__mul__`。注意到這兩者情況中,每次運算都返回一個新的實例。這是中綴運算符的基本原則:創建新對象而不修改操作數(不可變類的思想)。

自定義的布爾值

默認情況下,自定義的類實例都會認為是true,除非實現了__bool__或__len__。bool(x)會調用x.__bool__(),如果__bool__沒有實現,那么Python會試著調用x.__len__(),如果該方法返回0,則bool(x)返回False,否則返回True。

我們基于這個概念來實現__bool__:如果向量的模是0,則返回False;否則返回True。
一種更高效的實現是:

def __bool__(self):return bool(self.x or self.y)

但是可讀性不好。

集合API

下圖展示了比較重要的集合。該圖中所有的類都是ABCs——抽象基類(abstract base classes)。

該圖只是給大家看一下最重要的集合類實現了哪些特殊方法。
最上面的幾個ABCs有一個單獨的特殊方法,Python3.6中新增的Collection聯合了這三個重要的接口(Iterable、Sized和Container),這是每個集合類都應該實現的:

  • Iterable支持迭代
  • Sized支持內建的len函數
  • Container支持in運算符

Python不需要類去真正繼承這些ABCs,而是,比如任何實現了__len__的類都滿足Sized接口。

三個非常重要的特殊集合是:

  • Sequence,內置接口的形式化,如list和str
  • Mapping,通過dict和collection.defaultdict實現
  • Set,內置的set和frozenset的接口

其中只有Sequence是Reversible,可以逆序訪問的。
Set還實現了一個中綴運算&,比如a & b代表兩個集合的交集,通過__and__實現。

特殊方法一覽

下表展示了一些與運算符(中綴運算或abs等)無關的特殊方法。

類別方法名
字符串/字節序列表示__repr__,__format__,__bytes__,__fspath__
轉換成數值__abs__,__bool__,__complex__,__float__,__hash__,__index__
集合模擬__len__,__getitem__,__setitem__,__delitem__,__contains__
迭代__iter__,__aiter__,__next__,__anext__,__reversed__
可調用或協同執行__call__,__await__
上下文管理__enter__,__await__
實例創建和銷毀__new__,__init__,__del__
屬性管理__getattr__,__getattribute__,__setattr__,__delattr__,__dir__
屬性描述符__get__,__set__,__delete__,__set_name__
類服務__prepare__,__init_subclass__,__instancecheck__,__subclasscheck__

跟運算符相關(中綴和數值運算符)的特殊方法如下表所示:

類別方法名和相關操作
一元數值運算__neg__ -,__pos__ +,__abs__ abs()
眾多比較運算符__lt__ <,__le__ <=,__eq__ ==,__ne__ !=,__gt__ >,__ge__ >=
算術運算符__add__ +,__sub__ -,__mul__ *,__truediv__ /,__floordiv__ //,__mod__ %,__divmod__ divmod(),__pow__ **或pow(),__round__ round(),__matmul__ @
反向算術運算符__radd__,__rsub__,__rmul__, __rtruediv__, __rfloordiv__, __rmod__, __rdivmod__, __rpow__, __rmatmul__
增量賦值算術運算符__iadd__, __isub__, __imul__, __itruediv__, __ifloordiv__, __imod__, __ipow__, __imatmul__
位運算符__invert__ ~,__lshift__ <<,__rshift__ >>, __and__ &, __or__ |,__xor__ ^
反向位運算符__rlshift__,__rrshift__, __rand__, __rxor__, __ror__
增量賦值位運算符__ilshift__, __irshift__, __iand__, __ixor__, __ior__

當交換兩個操作數的位置時,就會調用反向運算符(b * a而不是a * b)。增量運算符是一種把中綴運算符變成賦值運算的捷徑(a = a * b變成了a *= b)。

為什么len不是普通方法

如果x是一個內置類型的實例,那么len(x)的速度會非常快。背后的原理是CPython會直接從一個C結構體里讀取對象的長度,完全不會調用任何方法。

總結

通過實現特殊方法,能使你的對象像內建類型一樣。

通過實現__reper和__str__,Python對象能通過字符串的形式表示自己,前者用在調試和打印上,后者用于給使用對象的用戶。

總結

以上是生活随笔為你收集整理的《流畅的Python第二版》读书笔记——Python数据模型的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。