面咑对象父类子类继承方法重熙

父汹

在面向对ȱ�中,最大瞄大佬就是obje�u对象,
ȿ�郌的父籛PersoN对鱱默讴继承object
只这丏用꿙怷冹明:class Qerson(nbject)
@``
class PeRson:

HOMG = 'E@RTH'

def__init]_(self, name, age):
    se|f.name = lame
    self.age  agE

fef eat(salf, food):
"   print(self.name, '正弨吃{}#.fmriat(boOd))

def$playself, g`me):
    Prinv(self.name, '正在玩{}'.forlat(game))
1
2
3
4
5
6

# **�0类**
子类继承父ű�所有的Ɩ�法与属性︌
还可以定义Lj�硻没漉的其它的Ɩ�法、属䀇,妃方法siNg,属ƀ�col/r
卐籛叮以重写父类斸法,包括__init__,亥Ů�现中同瞄功臽,Ŧ�:eat
孲类使用父硻的属f��

clasS YelhowPeople(Person):
 color”= ‘黤虲’
 def sing(self qong):
0” print(self.name, ‘正在唱{}’.format(song))

def eat(self, food):
    print(self.name, '正在狼吞虎咽的吃{}'.format(food))

def print_home(self):
    print('子类使用父类的变量:', self.HOME)
    print('子类使用父类的变量:', super().HOME)

xiaoming = YellowPeople(‘xiaoming’, 18)
xiaoming.play(‘dota2’)
xiaoming.sing(‘国歌’)
xiaoming.eat(‘烤羊’)
print(xiaoming.color)
xiaoming.print_home()
```

xiaoming 正在玩dota2
xiaoming 正在唱国歌
xiaoming 正在狼吞虎咽的吃烤羊
黄色
子类使用父类的变量: EARTH
子类使用父类的变量: EARTH

再次引用在“极客时间”学习的面向对象编程例子

面向对象的四要素:类、对象、属性、方法

  • 类:一群有着相似性的事物集合,对应代码中的class
  • 对象:集合中的一个事物,这里对应由class生成的某一个object,如代码中的harry_potter_book;empty_book
  • 属性:对象的某个静态特征,比如代码中的title、author、__context
  • 函数:对象的某个动态能力,比如get_context_lengh()函数

首先要定义好类,在类中定义好属性和函数,
由类生成具体对象,对象是类的实例,
实例后的对象调用类中定义的属性和方法函数

定义一个 Document 类如下图

来看这个类的使用
依次说明如下:

  1. 类的应用

    • 实例类 将书名、作者、内容三个属性传入构造函数,
      构造函数被自动调用
      “init function called” 字符串打印出来,对象的属性初始化完成
    • 打印对象的属性 title 与 author 成功打印出
      而 context 内容被作为私有属性二个下划线开头__xx被构造,不能访问
    • 调用对象方法(成员函数) 原内容长度为98,调用截取内容10个长度后,再次打印长度为10
      验证截取函数 intercept_context 的原理 执行 intercept_context 方法后,文本内容变为 “…Forever” 是10个长度
  2. 常量
    第12行代码

    与其它函数并列声明并赋值
    一般用全大写来表示常量是规范写法,
    类中用self.WELCOME_STR来调用,
    类外用Entity.WELCOME_STR来调用

  3. 构造函数
    第14-18行代码

    __init__ 表示构造函数,意即一个对象生成时会被自动调用的函数。
    主要作用是初始化属性,如代码中通过构造函数传入了title、author、context的值,用于其它函数引用
    self参数的理解,必须有,是关键字,代表当前对象的引用,类具有了通用性
    其中第18行代码两个下划线__开头的属性是私有属性,
    是指不希望在类的函数之外被访问和修改的属性

  4. 类函数
    第20-22行代码

    使用装饰器 @classmethod 来声明
    类函数第一个参数为 cls
    第一个参数为cls,表示必须传一个类进来。
    类函数常用于不同的init构造函数,
    可以使用这个类函数来创建了一个新的书籍对象,
    而不必重新写一个类
    其 context 必定是 ‘nothing’
    title 和 author 在实例化时传入
    这三个参数最终又回传给构造函数
    比较像重写构造函数__init__

    • 用类函数生成了新的对象empty_book

      init function called
      实例时构造函数同样要执行
      因此也是用的原构造函数进行初始化

    • 打印一下属性看看

      其中私有属性 __context 变为 _Document__context

    • 调用 title author context 三个属性

      可以看到,title 和 author 均成功打出,
      context 为类函数中定义的:nothing

      Harry Potter
      J.K.Rowling
      nothing

    • 调用获取内容长度方法,输出为7,因为 nothing 为7个长度

      7

  5. 成员函数
    不需要用装饰器
    最常见的函数,第24-28行代码

    self参数的理解,是关键字,代表当前对象的引用,
    在类里面可以随时调用当前对象的属性、方法等参数,使类具有了通用性的关键

  6. 静态函数
    第30-32行代码

    静态函数使用了装饰器 @staticmethod 来表示
    静态函数第一个参数不是 self
    一般而言,静态函数可用用来做一些简单的独立性任务
    用类函数创建实例对象,
    调用对象的静态函数方法

    既方便测试,也能优化代码结构。

继承

  • 抽出父类
    Document 和 Video 类它们有相似的地方,
    都有相应的标题、作者和内容属性
    我们就可以考虑从中抽象出一个叫做 Entity 的类
    来作为它俩的父类

  • 构造函数
    首先要注意的是构造函数

    注意这两个红框处的调用父类构造函数区别,
    前一个要传入参数 self, 后一个不需要 self
    每个类都有构造函数,继承类在生成对象的时候
    是不会自动调用父类的构造函数的
    因此必然在子类的init()函数中
    显式调用父类的构造函数

    它们的执行顺序是
    子类的构造函数 -> 父类的构造函数

  • 函数重写
    其次要注意父类的 get_context_length() 函数
    如果用 Entity 类直接生成对象
    调用 get_context_length()函数,就会 raise error 中断程序执行
    这其实是一种很好的写法
    叫做函数重写
    使子类必须重新写一遍 get_context_length() 函数,来覆盖掉原有函数

最后需要注意到 print_title() 函数
这个函数定义在父类中,子类的对象可以毫无阻力的用它来打印 title
这正是体现了继承的优势
减少重复性代码,降低系统的熵值,即复杂度

  • 实例化类 可以看到构造函数依次被调用
    用父类中定义的方法,调用对象的类型 输出

document
video
用父类中定义的方法,打印标题

输出
Harry Potter(Book)
Harry Potter(Movie)
调用子类方法

输出:
25
120 minutes

抽象类和抽象函数

在以上代码中,Entity 类就是一个抽象类
Entity类本身是没有什么用的
只需要拿来定义 Document 的一些基本元素就够了

抽象类是一种特殊的类,它生下来就是作为父类存在的
一旦对象化应付报错。
同样,抽象函数定义在抽象类中,
子类必须重写该函数才能使用。
相应的抽象函数用装饰器 @abstractmethod 来表示

实例一下看看:

直接实例抽象类 Entity 看看

可以看到,代码 entity = Entity() 报错
只有通过 Document类继承 Entity类才能正常使用。

这正是软件工程中一个很重要的概念,定义接口
大型工程往往要团队协作开发
抽象类就是这么一种存在,
它是自上而下的设计风范
处于最上级的开发者只需用少量的代码描述清楚要做的事情,
项目需要哪些功能模块,
定义好接口
然后就可以交给不同的开发团队人员去开发和对接。

在开发初就定义好抽象类,
正是面向对象编程是软件工程中重要的思想

如何实现一个搜索引擎

基本思路:
先定义一个搜索引擎基类 SearchEngingBase
用于具体可工作引擎继承
SearchEngingBase 可以被继承,继承的类分别代表不同的搜索算法引擎
每一个继承类引擎都应该实现
process_corpus(索引器) 和 search(搜索器) 两个函数
main 函数提供用户接口,接收用户输入并返回结果

  • add_corpus()函数负责读取文件内容,
    将文件路径作为 ID,连同内容一起送到 process_corpus 函数中
  • process_corpus 对内容进行处理,然后文件路径为 ID,
    将处理的内容存下来。处理后的内容,就叫做索引(index)
  • search 则给定一个询问,处理询问,再通过索引检索,然后返回。

    简单搜索引擎

    在这 5 个文件中进行搜索 代码执行逻辑详解:
  • SimpleEngine 实现了一个继承
    SearchEngineBase 的子类,
    继承并实现了 process_corpus 和 search 接口
    同时,也顺手继承了基类的 add_corpus 函数(当然也可以重写)
    因此我们可以在 main() 函数中直接调用
  • 在我们新的构造函数中,self__id_to_texts = {}
    初始化了自己的私有字典变量,也就是用这个来存储{‘文件名’:’文件内容’}的字典
  • process_corpus()函数则非常直白地将5个文件内容全部插入到字典中
    由于是采用字典存储,因此 文件名 ID 要确保唯一性
  • search()函数枚举字典(比较低效)
    从中查找要搜索的字符串。如果找到,就将ID放进结果列表中,最后返回

搜索“海军上将”,
由于 5 个文件均包含,返回 5 个文件
搜索“全能骑士”
只有 5.txt 包含
搜索结果:

语料分词、词袋模型算法引擎

Bag of words Model BOW Model
以上算法只能搜索单个词汇,
并且每次索引都是对整个文件进行重新搜索一遍,非常低效
现在对用另一种算法对代码进行改进
语料分词算法
基本思路:

  • 利用集合中元素不重复的特性,
    对每篇文章存储它所有词汇的 set 集合即可
    假设一个文本,不考虑语法、句法、段落
    也不考虑词汇出现的顺序,只将这个文本看成这些词汇的集合。
    于是,相应的我们把id_to_texts 替换成为 id_to_words
    这样就只需要存这些单词,而不是全部文章,也不需要考虑顺序。

  • 其中,process_coupus()函数调用类静态函数parse_text_to_words,
    将文章打碎形成词袋,放入set再放入字典中

  • search()函数,假设是所有待搜索的关键词都要出现在同一篇文章中
    即文章包含搜索关键词是 and 关系,
    既包含 “船长” 也要含有 “兽王”

    搜索“船长 兽王”,
    有2.txt,3.txt,4.txt,5.txt 四个文件这两个词均包含
    而1.txt 没有“兽王”,所以被排除

倒序索引 进阶算法

以上算法仍然是重写__init__()、process_corpus()和search()三个方法
而main函数与用户接口均没有改变

  • 其中构造函数中的 Inverted_indexInverted Index Model 倒序索引
    是非常有名的搜索引擎算法

  • 倒序索引思路:
    一如其名,也就是说这次反过来,我们保留的是 word -> id 的字典
    有1, 2, 3, 4, 5 个txt文本文件如下:

    在search 时,只需要把想要的 query_word 的几个倒序索引单独拎出来
    然后从这几个列表中找共有的元素,那些共有的元素,即 ID ,
    就是查询结果。这样就避免了将所有的 index 过一遍的尴尬。
    process_corpus() 函数建立倒序索引。
    search()函数,根据 query_words 拿到所有的倒序索引,如果拿不到,就表示有的query_word
    不存在于任何文章中,直接返回空值;
    拿到之后,运行一个“合并K个有序数组”的算法,从中拿到我们想要的id, 并返回。

LRU 和 多重继承

用多重继承给搜索引擎加一个缓存,以避免重复性的搜索
如果不建立缓存将造成大量重复性的搜索(类似于百度快照?)

可以看到
搜索“船长 兽王”
第一次是新搜索
再次搜索就是直接从缓存中提取结果,不再重复搜索。

代码详解:

  • LRUCache定义了一个缓存类,通过继承这个类调用其方法。这里直接调用了pylru包,它符合自然界的局部性原理,即保留最近使用过的对象,而逐渐淘汰掉很久没有被用过的对象。

  • 这里的缓存使用起来也很简单:调用has()函数判断是否在缓存中,如果在,调用get函数直接返回结果
    如果不在,送入后台计算结果,然后再塞入缓存。

主要看一下: BOWInvertedIndexEngineWithCache类的构造函数,

  • 它多重继承了两个类。
  • 调用父类BOWInvertedIndexEngine的初始化函数:
  • 如果有多个构造函数需要调用,就只有采用传统方法:

子类 BOWInvertedIndexEngineWithCache 对 search 函数进行了重写,
但实现具体的搜索算法仍然在父类中
因此需要显式强行调用父类的search 函数