抽象基類寫法高版本向後兼容
abc模塊
python3.3之前(不包括3.3)
- abc模塊還定義@abstractmethod、@abstractclassmethod、@abstractstaticmethod和@abstractproperty 三個裝飾器
- 從Python 3.3起廢棄了@abstractclassmethod、@abstractstaticmethod和@abstractproperty 三個裝飾器。因爲裝飾器可以在@abstractmethod上堆疊,那三個就顯得多餘了.例如聲明抽象類方法的推薦方 式是
class MyABC(abc.ABC):
@classmethod
@abc.abstractmethod
def an_abstract_classmethod(cls, ...):
pass
@abc.abstractmethod和def之間不能有其他裝飾器。
python3.4及其後
from abc import ABC, abstractmethod
class Foo(ABC):
@abstractmethod
def fun(self):
"""please Implement in subclass"""
print('please Implement in subclass')
class SubFoo(Foo):
def fun(self):
print('fun in SubFoo')
a = SubFoo()
a.fun()
python3.0到python3.3
from abc import ABCMeta, abstractmethod
class Bar(metaclass=ABCMeta):
@abstractmethod
def fun(self):
"""please Implement in subclass"""
print('please Implement in subclass')
class SubBar(Bar):
def fun(self):
print('fun in SubFoo')
b = SubBar()
b.fun()
python2
from abc import ABCMeta, abstractmethod
class Base():
__metaclass__ = ABCMeta
@abstractmethod
def fun(self):
"""please Implement in subclass"""
print('please Implement in subclass')
class SubBase(Bar):
def fun(self):
print('fun in SubBase')
c = SubBase()
c.fun()
通過註冊實現抽象基類
虛擬子類:指的是不通過繼承而利用註冊把一個類變成抽象基類的子類。
註冊虛擬之類的方式是調用register方法,語法是@抽象基類名稱.register。
註冊虛擬子類的方式是在抽象基類上調用register方法, 經註冊後的虛擬子類可以被issubclass和isinstance等函數識別,但是註冊的類不會從抽象基類中繼承任何方法或屬性。具體可通過類屬性__mro__查看類的真實繼承關係
白鵝類型的一個基本特性(也是值得用水禽來命名的原因):即便不繼承,也有辦法把一個類註冊爲抽象基類的虛擬子類.這樣做時我們保證註冊的類忠實地實現了抽象基類定義的接口,而Python會相信我們,從而不做檢查.如果我們說謊了,那麼常規的運行時異常會把我們捕獲.
import abc
class Tombola(abc.ABC):
@abc.abstractmethod
def load(self, iterable):
"""從可迭代對象中添加元素。"""
pass
@abc.abstractmethod
def pick(self):
"""隨機刪除元素,然後將其返回。
如果實例爲空,這個方法應該拋出`LookupError`。
"""
pass
def loaded(self):
"""如果至少有一個元素,返回`True`,否則返回`False`。"""
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))
register方法通常作爲普通的函數調用,不過也可以作爲裝飾器使用.我們使用裝飾器句法實現了TomboList類,這是Tombola 的一個虛擬子類.
from random import randrange
@Tombola.register
class TomboList(list):
def pick(self):
if self:
position = 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))
t = TomboList([12,23,34])
isinstance(t,Tombola) # True
Tombola.register(TomboList)
通過NotImplementedError實現抽象基類
只有子類實現了run方法才能運行run,否則會生成NotImplementedError錯誤。其中此時的Task是可以被實例化。
缺點:如果子類沒有實例化run方法, 子類也可以實例化,僅當子類調用了run方法,纔會報NotImplementedError錯誤
class Task():
def __init__(self, x, y):
self.x = x
self.y = y
def run(self):
raise NotImplemented('please Implement in subclass')
class SubTask(Task):
def __init__(self, x, y):
super().__init__(x, y)
def run(self):
print('run in SubTask')
d = SubTask(1, 3)
d.run()
自定義元類
自定義元類一般很少用到,基本abc模塊的元類可以滿足我們的需求
class TaskMeta(type):
def __new__(cls, name, bases, attrs):
new_class = super(TaskMeta, cls).__new__(cls, name, bases, attrs)
if attrs.pop('abstract', False):
return new_class
if not hasattr(new_class, 'run') or not callable(new_class.run):
raise TypeError('Please define "a run method"')
return new_class
class Task(metaclass=TaskMeta):
abstract = True
def __init__(self, x, y):
self.x = x
self.y = y
class SubTask(Task):
def __init__(self, x, y):
super().__init__(x, y)
def run(self):
print('Task(x=%s, y=%s)' % (self.x, self.y))
subclasshook
令抽象基類識別沒有進行子類化和註冊的類
參考: