• 8.12 定义接口或者抽象基类
    • 问题
    • 解决方案
    • 讨论

    8.12 定义接口或者抽象基类

    问题

    你想定义一个接口或抽象类,并且通过执行类型检查来确保子类实现了某些特定的方法

    解决方案

    使用 abc 模块可以很轻松的定义抽象基类:

    1. from abc import ABCMeta, abstractmethod
    2.  
    3. class IStream(metaclass=ABCMeta):
    4. @abstractmethod
    5. def read(self, maxbytes=-1):
    6. pass
    7.  
    8. @abstractmethod
    9. def write(self, data):
    10. pass

    抽象类的一个特点是它不能直接被实例化,比如你想像下面这样做是不行的:

    1. a = IStream() # TypeError: Can't instantiate abstract class
    2. # IStream with abstract methods read, write

    抽象类的目的就是让别的类继承它并实现特定的抽象方法:

    1. class SocketStream(IStream):
    2. def read(self, maxbytes=-1):
    3. pass
    4.  
    5. def write(self, data):
    6. pass

    抽象基类的一个主要用途是在代码中检查某些类是否为特定类型,实现了特定接口:

    1. def serialize(obj, stream):
    2. if not isinstance(stream, IStream):
    3. raise TypeError('Expected an IStream')
    4. pass

    除了继承这种方式外,还可以通过注册方式来让某个类实现抽象基类:

    1. import io
    2.  
    3. # Register the built-in I/O classes as supporting our interface
    4. IStream.register(io.IOBase)
    5.  
    6. # Open a normal file and type check
    7. f = open('foo.txt')
    8. isinstance(f, IStream) # Returns True

    @abstractmethod 还能注解静态方法、类方法和 properties 。你只需保证这个注解紧靠在函数定义前即可:

    1. class A(metaclass=ABCMeta):
    2. @property
    3. @abstractmethod
    4. def name(self):
    5. pass
    6.  
    7. @name.setter
    8. @abstractmethod
    9. def name(self, value):
    10. pass
    11.  
    12. @classmethod
    13. @abstractmethod
    14. def method1(cls):
    15. pass
    16.  
    17. @staticmethod
    18. @abstractmethod
    19. def method2():
    20. pass

    讨论

    标准库中有很多用到抽象基类的地方。collections 模块定义了很多跟容器和迭代器(序列、映射、集合等)有关的抽象基类。numbers 库定义了跟数字对象(整数、浮点数、有理数等)有关的基类。io 库定义了很多跟I/O操作相关的基类。

    你可以使用预定义的抽象类来执行更通用的类型检查,例如:

    1. import collections
    2.  
    3. # Check if x is a sequence
    4. if isinstance(x, collections.Sequence):
    5. ...
    6.  
    7. # Check if x is iterable
    8. if isinstance(x, collections.Iterable):
    9. ...
    10.  
    11. # Check if x has a size
    12. if isinstance(x, collections.Sized):
    13. ...
    14.  
    15. # Check if x is a mapping
    16. if isinstance(x, collections.Mapping):

    尽管ABCs可以让我们很方便的做类型检查,但是我们在代码中最好不要过多的使用它。因为Python的本质是一门动态编程语言,其目的就是给你更多灵活性,强制类型检查或让你代码变得更复杂,这样做无异于舍本求末。

    原文:

    http://python3-cookbook.readthedocs.io/zh_CN/latest/c08/p12_define_interface_or_abstract_base_class.html