四目以待 發表於 2019-7-21 11:12:00

Python中的抽象基类

<h2 id="1说在前头">1.说在前头</h2>
<p>"抽象基类"这个词可能听着比较"深奥",其实"基类"就是"父类","抽象"就是"假"的意思,</p>
<p>"抽象基类"就是"假父类."</p>
<h2 id="2对之前元类的一点补充">2.对之前元类的一点补充</h2>
<p>之前说过通过元类实例化类的语法是</p>
<pre><code class="language-python">变量名 = type("类名", ("继承的类",), {"属性名":"属性值"})
</code></pre>
<p>现在介绍另一种方法</p>
<pre><code class="language-python">class 类名(metaclass=元类名):
    ...
</code></pre>
<p>举个例子:</p>
<pre><code class="language-Python">#!/usr/bin/python3
# -*- coding: utf-8 -*-
# __author__: kainhuck

# 定义一个元类
class MyMetaClass(type):
    def __new__(cls, name, bases, attrs):
      '''
      :param name: 类名
      :param bases: 继承的基类
      :param attrs: 拥有的属性

      要求必须含有name属性

      '''
      name = attrs.get("name", None)
      if name and not callable(name):
            return type.__new__(cls, name, bases, attrs)
      raise NotImplementedError("必须含有name属性")


# 定义普通类
# class MyClassA(metaclass=MyMetaClass):   # 报错,没有定义name属性
#   pass

# 定义普通类
# class MyClassB(metaclass=MyMetaClass):# 报错,没有定义name属性
#   def name(self):
#         pass

#
# class MyClassC(metaclass=MyMetaClass):# 报错,没有定义name属性
#   def __init__(self):
#         self.name = "kainhuck"

#
class MyClassD(metaclass=MyMetaClass):# 没有报错
    name = "kainhuck"
</code></pre>
<h2 id="3鸭子类型">3.鸭子类型</h2>
<p>鸭子类型:如果一个东西看起来想一个鸭子,叫起来像一个鸭子,那么它大概就是一只鸭子.</p>
<p>在Python中有些时候我们需要一个有某个功能(比如说:鸭子叫)的对象,那我们可以通过判断这个对象是不是一只鸭子来检测是否满足我们的需求;但仔细想想这有些缺陷,因为我们真正需要的是<code>鸭子叫</code>这个方法,一个对象无论是不是鸭子只要他会像鸭子一样叫就可以啦.</p>
<p>这话可能有些绕,让我来举一个例子</p>
<p>有这么一个函数:</p>
<pre><code class="language-python">def func(something):
    print(something)
</code></pre>
<p>这个函数会打印出参数的第一个元素,其实这里隐含着一个条件--参数支持下标索引.为了使代码完善我们应该对该函数做点修改.</p>
<p>方案一.</p>
<pre><code class="language-Python">def func(something):
    if isinstance(something, (list, tuple, set)):        # 这些方法支持下标索引
      print(something)
    else:
      print("Error")
</code></pre>
<p>方案一的缺点:</p>
<blockquote>
<p>这样写就默认把something的类型限定了,拓展性很差</p>
<p>我们知道只要自定义的类实现了<code>__getitem__</code>方法就可以使其支持下标索引.</p>
</blockquote>
<p>方案二.</p>
<pre><code class="language-Python">def func(something):
    if hasattr(something, '__getitem__'):       
      print(something)
    else:
      print("Error")
</code></pre>
<p>方案二的缺点:</p>
<blockquote>
<p>并不是所有实现<code>__getitem__</code>的方法都可以支持下标索引,比如<code>字典</code>类型</p>
</blockquote>
<p>这样似乎没有解决方案了..其实抽象基类就完美的解决了这问题</p>
<h2 id="4抽象基类">4.抽象基类</h2>
<h3 id="1-抽象基类的定义">1. 抽象基类的定义:</h3>
<p>由abc.ABCMeta这个元类实现的类就是抽象基类,如</p>
<pre><code class="language-Python">class AbstractClass(metaclass=abc.ABCMeta):
    pass
</code></pre>
<h3 id="2-register方法">2. register方法</h3>
<p>定义好的抽象基类通过<code>register</code>方法可以成为别的类的父类</p>
<p>举个例子:</p>
<pre><code class="language-Python">import abc

# 定义一个抽象基类
class AbstractClass(metaclass=abc.ABCMeta):
    pass

# 定义一个普通类继承自object
class MyClass(object):
    pass

# 把我们定义的抽象基类注册为MyClass的父类
AbstractClass.register(MyClass)
mc = MyClass()
print(issubclass(MyClass, AbstractClass))# 输出True
print(isinstance(mc, AbstractClass))# 输出True

# 将我们定义的抽象基类注册到系统定义的类
AbstractClass.register(list)

print(isinstance([], AbstractClass))    # 输出True
</code></pre>
<p><em>说明一点:抽象基类虽然可以成为别的类的父类,但是别的类并不会继承抽象基类的方法和属性</em></p>
<h3 id="3-对前面例子的方案三实现">3. 对前面例子的方案三实现</h3>
<p>方案三.</p>
<pre><code class="language-python">from abc import ABCMeta

class MySequence(metaclass=ABCMeta):
    pass

MySequence.register(list)        # 注册为列表的父类
MySequence.register(tuple)        # 注册为元组的父类

'''
也可以自定义一个类,将MySequence注册为其父类
'''
</code></pre>
<pre><code class="language-python">def func(something):
    if isinstance(something, AbstractClass):        # AbstractClass的子类
      print(something)
    else:
      print("Error")
</code></pre>
<h3 id="4__subclasshook__魔法方法">4.<code>__subclasshook__</code>魔法方法</h3>
<p>看过上面的例子你们肯定会觉得,给每个类都注册一遍抽象基类太麻烦了,没错Python的开发者也这么觉得,于是<code>__subclasshook__</code>这个方法出现了</p>
<p>几点说明:</p>
<ol>
<li>该方法定义在抽象基类中</li>
<li>该方法必须定义为类方法</li>
<li>该方法有三个返回值
<ol>
<li>True: 如果测试类被认为是子类</li>
<li>False: 如果测试类不被认为是子类</li>
<li>NotImplemented: 这个后面讲</li>
</ol>
</li>
</ol>
<p>定义一个抽象基类:</p>
<pre><code class="language-Python">import abc

class AbstractDuck(metaclass=abc.ABCMeta):
    @classmethod
    def __subclasshook__(cls, subclass):
      quack = getattr(subclass, 'quack', None)# 取出subclass的 quack 属性,如果不存在则返回 None
      return callable(quack)# 返回quack是否可以调用(是否是个方法)
</code></pre>
<p>定义两个测试类</p>
<pre><code class="language-Python">class Duck(object):
    def quack(self):
      pass


class NotDuck(object):
    quack = "foo"
</code></pre>
<p>判断是否是AbstractDuck的子类</p>
<pre><code class="language-python">print(issubclass(Duck, AbstractDuck))# 输出 True
print(issubclass(NotDuck, AbstractDuck))# 输出 False
</code></pre>
<p>注意:<code>__subclasshook__</code>方法的优先级大于<code>register</code></p>
<p>举个例子解释<code>NotImplemented</code>返回值</p>
<pre><code class="language-python">In : import abc                                                                                                                                 

In : class AbstractDuck(metaclass=abc.ABCMeta):
   ...:   @classmethod
   ...:   def __subclasshook__(cls, subclass):
   ...:         quack = getattr(subclass, 'quack', None)# 取出subclass的 quack 属性,如果不存在则返回 None
   ...:         if callable(quack):
   ...:             return True
   ...:         return NotImplemented
   ...:                                                                                                                                                                                                                                                                                 

In : class Duck(object):
   ...:   def quack(self):
   ...:         pass
   ...:                                                                                                                                             

In : class NotDuck(object):
   ...:   quack = "foo"
   ...:                                                                                                                                                                                                                                                         

In : issubclass(NotDuck, AbstractDuck)                                                                                                         
Out: False

In : AbstractDuck.register(NotDuck)                                                                                                            
Out: __main__.NotDuck

In : issubclass(NotDuck, AbstractDuck)                                                                                                         
Out: True

</code></pre><br><br>
来源:https://www.cnblogs.com/kainhuck/p/11220549.html
頁: [1]
查看完整版本: Python中的抽象基类