Python——抽象基類

在處理編程和對象時,強調構成問題而不是身份問題,強調hasattr函數而不是isinstance函數。抽象基類是一個分配身份的機制。抽象基類也提供了一個標明抽象方法的機制,就是要求其他實現提供關鍵性功能,這些功能是在基類中實現中不主動提供的功能。

抽象基類提供了聲明一個類是另一個類的派生類的機制(無論它是否是另一個類的派生類),該機制沒有影響實際的對象繼承關係或是改變方法解析順序。

抽象基類提供了一種要求子類實現指定協議的方式。如果一個抽象基類要求實現指定方法,而子類沒有實現這個方法,然後當試圖創建子類時會拋出錯誤。

abc模塊是表示抽象基類的模塊,它提供一個名為ABCMeta的元類,任何抽象基類必須使用ABCMeta元類。

聲明虛擬子類
>>>import abc
>>>class AbstractDict(metaclass = ABCMeta):
def foo(self):
return None

>>>AbstractDict.register(dict)
<class dict>

>>>{}.foo()
Traceback
...
...AttributeError:dict object has no attribute foo
dict的方法解析並沒有發生改變
>>>isinstance({}, AbstractDict)
True
>>>isinstance(AbstractDict, {})
False

register方法是ABCMeta元類的類所提供的方法,用來註冊應該被註冊為ABCMeta子類的新類。

>>>@AbstractDict.register
...class DictLikeAbstract(object):
... pass

>>>issubclass(DictLikeAbstract, AbstractDict)
>>>True

__subclasshook__:此方法必須定義為一個類方法,並且使用@classmethod裝飾器定義,接受一個額外的位置參數是被測試的類

>>> import abc
>>> class AbstractDuck(object):
__metaclass__ = abc.ABCMeta

@classmethod
def __subclasshook__(cls, other):
quack = getattr(other, quack, None)
return callable(quack)

>>> class Duck(object):
def quack(self):
pass

>>> class NotDuck(object):
quack = foo

>>> issubclass(Duck, AbstractDuck)
True
>>> issubclass(NotDuck, AbstractDuck)
False
>>> AbstractDuck.register(NotDuck)
>>> issubclass(NotDuck, AbstractDuck)
False

如果子類中有可調用的quack方法則是AbstractDuck的子類。__subclasshook__方法被定義時優於register方法。

>>> class Task(object):
def __init__(self):
self.runs = []
def run(self):
start = datetime.now()
result = self._run()
end = datetime.now()
self.runs.append({
start: start,
end: end,
result: result,
})
return result
def _run(self):
raise NotImplementedError(Task subclasses must define a _run method.)

>>> t = Task()
>>> t.run()

Traceback (most recent call last):
File "<pyshell#39>", line 1, in <module>
t.run()
File "<pyshell#37>", line 6, in run
result = self._run()
File "<pyshell#37>", line 15, in _run
raise NotImplementedError(Task subclasses must define a _run method.)
NotImplementedError: Task subclasses must define a _run method.

Task提供一個shell方法_run,任何未能重寫_run的子類大都會引發NotImplementedError

>>> class SubTask(Task):
def _run(self):
return 2+2

>>> subtask = SubTask()
>>> subtask.run()
4

抽象基類可以被明確提出作為滿足特定需求的解決方案,依據這一概念,理想情況下使用抽象基類後應該僅有一種實現這種特定需求的正確方法。

抽象屬性
>>> class AbstractClass(object):
__metaclass__ = abc.ABCMeta
@abc.abstractproperty
def foo(self):
pass

>>> class InvalidChild(AbstractClass):
pass

>>> ic = InvalidChild()

Traceback (most recent call last):
File "<pyshell#55>", line 1, in <module>
ic = InvalidChild()
TypeError: Cant instantiate abstract class InvalidChild with abstract methods foo

>>> class ValidChild(AbstractClass):
def foo(self):
return foo

>>> vc = ValidChild()
>>> vc.foo()
foo
>>> InvalidChild.foo()

Traceback (most recent call last):
File "<pyshell#62>", line 1, in <module>
InvalidChild.foo()
TypeError: abstractproperty object is not callable
重寫抽象方法的子類才能被實例化

抽象類或靜態方法

class AbstractClass(metaclass=abc.ABCMeta):
@classmethod
@abc.abstractmethod
def foo(cls):
return 42

>>>class InvalidChild(AbstractClass):
pass
>>>ic = InvalidChild()
Traceback
....
TypeError:Cant instantiate abstract class....
抽象方法本身能夠被直接調用,且不會引發任何錯誤
>>>InvalidChild.foo()
42

抽象基類最重要的作用是提供了一個正式和動態的方式來回答這個問題,『你正在獲取的對象是你認為的類型嗎』,它可以克服一些僅僅檢測特定屬性是否存在或僅僅檢測是否為特定類這兩種方法的缺點。

《Python高級編程》——讀書筆記

推薦閱讀:

TAG:Python | Python教程 |