4.2_Abstract base class (abc module)
Reference blog
1. Preface (nonsense, don’t read it)
An abstract base class is like java
an interface in java
China , where it cannot implement multiple inheritance, that is, it can only inherit one class, but it can inherit multiple interfaces, and interfaces cannot be used for instantiation.
The python
abstract base class cannot be instantiated either. python
It is a dynamic language, and there is no need to declare variable types when defining variables. A variable is just a symbol and can point to any type of object.
We can copy any type of data to python
any variable in and can modify it. There is no need to java
implement a polymorphism like that, because python
it is a language that supports polymorphism.
Compared with static languages, dynamic languages do not need to specify variable types. This will cause if you python
write wrong code in it, you will find the error only when you run it, that is, you cannot do type checking.
When we use the design python class, we must put the duck type in the first place. What characteristics or types a class has depends on what magic functions it implements inside. Different magic functions give the class different characteristics.
Duck types and magic functions form the basis of the entire python language, which can also be said to be a protocol in python. It can be said that python itself does not implement certain features by inheriting a certain class or a certain interface, but by specifying the class of magic functions to make it into a certain type of object. For example, the string __repr__
representation: , __str__
; iteration Related: __iter__
, __next__
; you can call: __call__
and so on. Follow this protocol when programming.
Two, abstract base class
- Set some methods in the base class. All classes that inherit this base class must override the methods in this abstract base class.
- Abstract base classes cannot be used for instantiation.
- The role of the abc module:
Python itself does not provide abstract classes and interface mechanisms. If you want to implement abstract classes, you can use the abc module. abc is the abbreviation of Abstract Base Class.
Question
- Since python is designed based on the duck type, why does it have the concept of abstract base class? Isn't it enough to implement certain methods directly? Why do you have to inherit?
Answer
Two application scenarios of abstract base class
- The first application is to check whether a certain class has a certain method (determine the type of an object)
class Company(object):
def __init__(self,employee_list):
self.employee = employee_list
def __len__(self):
return len(self.employee)
com=Company(['ming','ming2'])
print(hasattr(com,'__len__')) # True
# hasattr内置函数 某一个对象是否具有某种属性,类中函数其实就是一个属性
print(len(com)) # 2
So those who have learned java
it should know that we are more inclined to judge what type Company
of com
object the class instantiates . Instead of hasattr(com,'__len__')
judging an object by calling this method.
Based on the above content, the first application scenario of the abstract base class is led to determine the type of a certain type of object. If there is no abstract base class, hasattr()
this method must be used . The use of abstract base classes becomes the following way:
from collections.abc import Sized
isinstance(com,Sized) # True
# isinstance 判断某一对象是否是指定的类型
That is to say, you can have a __len__
method by inheriting Sized , and instance(对象, Sized)
return True at this time
Finally, take a look at the source code of the Sized abstract base class imported above
#所有的抽象基类中的metaclass都必须是ABCMeta
class Sized(metaclass = ABCMeta):
__slots__ = ()
@abstractmethod
def __len__(self):
return 0
@classmethod
def __subclasshook__(cls,C):
if cls is Sized:
return _check_methods(C,'__len__')
return NotImplemented
-
The second application is to force a certain subclass to implement certain methods
Such an internal implementation of a
web
framework (e.g.Django
), the frame may be desirablecache
cache integrated in, and may be desirable in the futureredis
orcache
ormemorychache
replaced, or may be usedredis
orcache
ormemorycache
customize a component, to replace in existingcache
short seamless integrated.So we need to design an abstract base class and specify that the subclass must implement certain methods. For example, when writing a system, when writing the framework, we don’t know whether people who use the framework will replace it with
redis
orcache
wait, but we hope that after writing these, users do not need or reduce the need to callredis
orcache
wait. Code, so an abstract base class (primary key) will be agreed in advance
class CacheBase():
def get(self,key):
pass
def set(self,key,value):
pass
CacheBase
The abstract base class defines get
methods (get data from the cache and get it key
) and set
methods (add value
). When inheriting this abstract base class, users must implement these two methods again (if you don’t implement the call, there will be an error, so Will force users to implement these methods).
If these interfaces are not defined in advance, the user will return to the class to rewrite the code in the later use; if a contract is made in advance, the user only needs to implement a subclass of the abstract class and configure it in the configuration file. The primary key is If implemented as agreed, it is very convenient to replace.
It is often necessary to consider when writing a framework, which is very helpful to the scalability of the system.
3. How to simulate an abstract base class?
code show as below
#假如有一个抽象基类CacheBase,用户在继承它时必须实现get,set方法
class CacheBase():
def get(self,key):
raise NotImplementedError
def set(self,key,value):
raise NotImplementedError
class RedisCache(CacheBase):
pass
redise_cache = RedisCache()
redis_cache.set('key','value') # NotImplementedError
# 调用set方法会报错
If the subclass reimplements the set
method, the method of the subclass will be called without throwing an exception.
class CacheBase():
def get(self,key):
raise NotImplementedError
def set(self,key,value):
raise NotImplementedError
class RedisCache(CacheBase):
def set(self,key,value):
pass
redise_cache = RedisCache()
redis_cache.set('key','value') # 无异常
The disadvantage of the above simulation is that this exception will be thrown when the set method is called. What if we want to throw an exception while initializing the object?
Need to use the abc
module, it has two, one is under the overall situation abc
, the other is collections
inside abc
.
abc
Implement abstract base classes with modules
import abc
class CacheBase():
@abc.abstractmethod #装饰器 抽象方法(设计方法)
def get(self,key):
pass
@abc.abstractmethod #装饰器 抽象方法(设计方法)
def set(self,key,value):
pass
class RedisCache(CacheBase): #没有重写方法
pass
redise_cache = RedisCache()
# redis_cache.set('key','value') # 无异常
The above code RedisCache
throws an exception during initialization because the subclass does not overload the methodTypeError: Can't instantiate abstract class RedisCache with abstract methods get,set
class RedisCache(CacheBase):
def get(self,key):
pass
def set(self,key,value):
pass
redise_cache = RedisCache() #RedisCache子类实现get和set方法后,运行不抛异常了
- Some general abstract base classes have been implemented in python, so that we can understand the interface of the data structure.
_collections_abc.py
You can see many defined abstract base classes at the beginning of the source file.
from abc import ABCMeta, abstractmethod
import sys
__all__ = ["Awaitable", "Coroutine",
"AsyncIterable", "AsyncIterator", "AsyncGenerator",
"Hashable", "Iterable", "Iterator", "Generator", "Reversible",
"Sized", "Container", "Callable", "Collection",
"Set", "MutableSet",
"Mapping", "MutableMapping",
"MappingView", "KeysView", "ItemsView", "ValuesView",
"Sequence", "MutableSequence",
"ByteString",
]
Just look at one, like the first oneAwaitable
class Awaitable(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __await__(self):
yield
@classmethod
def __subclasshook__(cls, C):
if cls is Awaitable:
return _check_methods(C, "__await__")
return NotImplemented
Among them __subclasshook__
this magic function is very important.
Recall a question: For the example given at the beginning com
is Company
an object instantiated by a Company
class , and the class does not inherit the abstract base class Sized, then why isinstance(com,Sized)
can it be determined that it com
is a Sized
type?
The answer is Sized
that __subclasshook__
this magic function is defined in the abstract base class. The source code is as follows (the complete is given above, and the part is briefly given here)
@classmethod
def __subclasshook__(cls,C):
if cls is Sized:
return _check_methods(C,'__len__')
return NotImplemented
__subclasshook__
The function first calls _check_methods
this function to see if the passed object C has a __len__
method, and if so, it returns True
. Sized
This magic function in the base class is also being abstracted so that it isinstance(com,Sized)
can return True
.
Of course, isinstance
as python
a built-in function, the function is definitely more than that simple. Not only call Sized
the __subclasshook__
functions in the class, but also do many other attempts. For example, to find an inheritance chain:
class A:
pass
class B(A):
pass
b = B()
print(isinstance(b,A)) # True
Summary: Try to use the python
duck type as much as possible when using it . It is very flexible. If you must inherit certain interfaces, it is recommended to use Mixin
(Python multiple inheritance) to achieve it. It is not recommended to use abstract base classes (then why I learn this ==), Because abstract base classes are easy to overuse and difficult to understand in design.