S11 介面:從協議到抽象基類 # random.shuffle 就地打亂 from random import shuffle l = list(range(10)) shuffle(l) print(l) shuffle(l) print(l) [0, 6, 3, 2, 4, 8, 5, 7, ...
S11 介面:從協議到抽象基類
# random.shuffle 就地打亂
from random import shuffle
l = list(range(10))
shuffle(l)
print(l)
shuffle(l)
print(l)
[0, 6, 3, 2, 4, 8, 5, 7, 1, 9]
[0, 5, 9, 7, 6, 2, 4, 8, 1, 3]
猴子補丁
import collections
Card = collections.namedtuple('Card', 'rank suit')
class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = 'spades diamondes 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]
# target: 洗牌
deck = FrenchDeck()
shuffle(deck)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In [4], line 3
1 # target: 洗牌
2 deck = FrenchDeck()
----> 3 shuffle(deck)
File c:\Users\qiany\AppData\Local\Programs\Python\Python39\lib\random.py:362, in Random.shuffle(self, x, random)
359 for i in reversed(range(1, len(x))):
360 # pick an element in x[:i+1] with which to exchange x[i]
361 j = randbelow(i + 1)
--> 362 x[i], x[j] = x[j], x[i]
363 else:
364 _warn('The *random* parameter to shuffle() has been deprecated\n'
365 'since Python 3.9 and will be removed in a subsequent '
366 'version.',
367 DeprecationWarning, 2)
TypeError: 'FrenchDeck' object does not support item assignment
# 打補丁
def set_card(deck, position, card):
deck._cards[position] = card
FrenchDeck.__setitem__ = set_card
shuffle(deck)
print(deck[:5])
[Card(rank='9', suit='spades'), Card(rank='2', suit='spades'), Card(rank='5', suit='spades'), Card(rank='Q', suit='clubs'), Card(rank='10', suit='hearts')]
定義抽象基類的子類
import collections
Card = collections.namedtuple('Card', 'rank suit')
class FrenchDeck2(collections.MutableSequence):
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = 'spades diamondes 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]
def __setitem__(self, position, value): # 欲實現 shuffle 須實現 __setitem__
self._cards[position] = value
def __delitem__(self, position):
del self._cards[position]
def insert(self, position, value):
self._cards.insert(position, value)
繼承 MutableSequence
必須實現 __delitem__
, insert
FrenchDeck2
- 從
Sequence
繼承了__contains__
,__iter__
,__reversed__
,index
,count
- 從
MutableSequence
繼承了append
,extend
,pop
,remove
,__iadd__
標準庫中的抽象基類
collections.abs
第一層:
Iterable
:Sequence
,Mapping
,Set
,Iterator
通過__iter__
方法支持迭代Container
:Sequence
,Mapping
,Set
通過__contains__
方法支持in
Sized
:Sequence
,Mapping
,Set
,MappingView
通過__len__
方法支持len()
Callable
: (None)Hashable
: (None)
第二層:
Sequence
:MutableSequence
Mapping
:MutableMapping
Set
:MutableSet
,ItemsView
,KeysView
MappingView
:ItemsView
,KeysView
,ValuesView
numbers
Number
Complex
Real
Rational
Intergal
eg. 檢查一個數是否為整數: isinstance(x, numbers.Integral)
isinstance(x, type)
- type 為
Intergal
檢查int
,bool
- type 為
Real
檢查int
,bool
,float
,fractions.Fraction
, Numpy中相關對象
檢查對象是否可以被 調用, 可用 callable()
檢查對象是否可以被 散列, 可用 isinstance(obj, Hashable)
import numbers
print(1, isinstance(233, numbers.Integral))
print(2, isinstance(233.33, numbers.Integral))
print(3, isinstance(233.00, numbers.Integral))
print(4, isinstance(True, numbers.Integral))
print(5, isinstance(False, numbers.Integral))
True
False
False
True
True
定義並使用一個抽象基類
Tombola:
-
抽象方法:
- load()
- pick()
-
具體方法:
- loaded()
- inspect()
import abc
class Tombola(abc.ABC):
@abc.abstractclassmethod # 一般 abstractclassmethod 只有 文檔字元串
def load(self, iterable):
'''可從迭代對象中添加元素'''
@abc.abstractclassmethod
def pick(self):
'''隨機刪除元素並返回
若果實例為空, 拋出 LookupError'''
def loaded(self): # 抽象基類可以包含具體實現方法
'''是否有元素'''
return bool(self.inspect()) # 抽象基類中的具體方法 只能依賴基類定義的介面(即該抽象基類中其他具體方法、抽象方法、特征)
def inspect(self):
'''返回一個由當前元素構成的有序元組'''
items = []
while True:
try:
items.append(self.pick())
except LookupError:
break
self.load(items)
return tuple(sorted(items))
抽象方法可以有實現代碼(不僅局限於文檔字元串)
但即使實現了,子類 必須 覆蓋抽象方法
或者用 super()
函數調用抽象方法
註: @abc.abstractmethod
和其他修飾器連用時, @abc.abstractmethod
應放在最內層
# 不符合 Tombola 的子類
class Fake(Tombola):
def pick(self):
return 13
f = Fake()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In [13], line 6
3 def pick(self):
4 return 13
----> 6 f = Fake()
TypeError: Can't instantiate abstract class Fake with abstract method load
# Tombola 的子類 BingoCage
import random
class BingoCage(Tombola):
def __init__(self, items):
self._randomizer = random.SystemRandom() # 調用 os.random() 函數, 生成"適合加密"的隨機位元組序列
self._items = []
self.load(items) # 委托 load 初始化
def load(self, items):
self._items.extend(items)
self._randomizer.shuffle(self._items) # 打亂
def pick(self):
try:
return self._items.pop()
except IndexError:
raise LookupError('pick from empty BingoCage')
def __call__(self):
self.pick()
class LotteryBlower(Tombola):
def __init__(self, iterable):
self._balls = list(iterable)
def load(self, iterable):
self._balls.extend(iterable)
def pick(self):
try:
position = random.randrange(len(self._balls))
except IndexError:
raise LookupError('pick from empty LotteryBlower')
return self._balls.pop(position)
def loaded(self): # 重寫 loaded
return bool(self._balls)
def inspect(self):
return tuple(sorted(self._balls))
虛擬子類
註冊虛擬子類 在抽象基類上調用 register
方法, 這樣, issubclass
和 isinstance
都能識別
但 註冊的類 不會從抽象基類中繼承如何方法或屬性
@Tombola.register # 註冊為 Tombola 的 虛擬子類
class TomboList(list): # 繼承 list
def pick(self):
if self: # 是否為空
position = random.randrange(len(self))
return self.pop(position)
else:
raise LookupError('pop from empty TomboList')
load = list.extend
def loaded(self):
return bool(self)
def inspect(self):
return tuple(sorted(self))
print(1, issubclass(TomboList, Tombola))
t = TomboList(range(100))
print(2, isinstance(t, Tombola))
print(3, TomboList.__mro__) # 按順序列出類及其超類
1 True
2 True
3 (<class '__main__.TomboList'>, <class 'list'>, <class 'object'>)
__subclasses__
返回類的直接子類列表, 不包含虛擬子類
_abc_registry
只有抽象基類有整個屬性(一個WeakSet對象)
本文來自博客園,作者:Zinc233,轉載請註明原文鏈接:https://www.cnblogs.com/Zinc233/p/FluentPython_S11.html