python2和python3中抽象基類寫法和__subclasshook__用法

抽象基類寫法高版本向後兼容

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

令抽象基類識別沒有進行子類化和註冊的類

參考:

  1. Python實現抽象基類的3三種方法
  2. 協議, 接口, 抽象類型
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章