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. 协议, 接口, 抽象类型
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章