面向对象基础

本章目录:

一.什么是面向对象编程(OOP)
二.类与对象
三.建立类和对象
四.初始化方法 __init__
五.属性查找
六.绑定方法与非绑定方法
七.对象之间交互
八.封装
九.继承
十.多态
十一.面向对象开发流程

 

一.面向对象初识

什么是面向过程

首先明确一点,在面向对象以前咱们一直都是按照面向过程的方式来编写程序!css

面向过程的程序设计:核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么......面向过程的设计就比如精心设计好一条流水线,是一种机械式的思惟方式。html

优势是:复杂度的问题流程化,进而简单化(一个复杂的问题,分红一个个小的步骤去实现,实现小的步骤将会很是简单)java

缺点是:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线没法生产汽车,即使是能,也得是大改,改一个组件,牵一发而动全身。node

应用场景:一旦完成基本不多改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。python

 

为何须要面向对象?

当今时代背景下,一般应用程序对扩展性和维护性要求都是很是高的,为何?想一想qq,微信,是否是不断的在添加新功能?,也就是说一款应用程序诞生后,都须要不断的更新维护linux

 

什么是面向对象编程

面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。git

它将对象做为程序的基本单元程序员

将数据和处理数据的程序封装到对象中web

以提升软件的重用性、灵活性和扩展性为首要目的算法

面向对象编程优缺点

案例分析1:把大象装进冰箱如何实现

案例分析2:要开一家公司

面向对象编程的优势:

  • 程序员的角色发送了改变,从一个操做者,变成了指挥者,再也不须要关心,每一个功能具体的实现细节,从而能够专一处理业务逻辑,是一种思想上的转变
  • 大大提升了程序的扩展性,当一个对象发生了修改时,对其余对象时没有任何影响的,对象之间相互独立,耦合度变得更低了
  • 提升了程序的灵活性,例如游戏中,每一个玩家的操做都是自由的,而不是机械般固定的!你能够买武器,也能够买护甲

缺点:

  • 编程的复杂度远高于面向过程,不了解面向对象而当即上手基于它设计程序,极容易出现过分设计的问题。一些扩展性要求低的场景使用面向对象会徒增编程难度,若是用面向对象来设计linux,估计如今都没写完
  • 没法向面向过程的程序设计流水线式的能够很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即使是上帝也没法准确地预测最终结果。

应用场景:

  • 需求常常变化的软件,通常需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方

 

用一个例子来讲明面向对象与面向过程的区别:

话说三国时期曹军于官渡大败袁绍,酒席之间,曹操诗兴大发,吟道:喝酒唱歌,人生真爽! 众将直呼:"丞相好诗",因而命印刷工匠刻板印刷以流传天下;

待工匠刻板完成,交与曹操一看,曹操感受不妥,说道:"喝酒唱歌,此话太俗,应改成'对酒当歌'较好",因而名工匠从新刻板,当时尚未出现活字印刷术,若是样板要改,只能从新刻板,工匠眼看连夜刻版之工,完全白费,心中叫苦连天。可也只得照办。

版样再次出来请曹操过目,曹操细细一品,以为仍是很差,说”人生真爽太过直接,应该改问语才够意境,所以应改成‘对酒当歌,人生几何?’“,因而....

在活字印刷术还没出现以前,若是版样有改动,只能从新雕刻。并且在印刷完成后,这个样板就失去了它的价值,若是须要其余样板只能从新雕刻。而活字印刷术的出现就大大改善了印刷技术。如上例”喝酒唱歌,人生真爽“,若是用活字印刷,只须要改四个字就可,其他工做都未白作。岂不快哉!!

活字印刷也反应了OOP。当要改动时,只须要修改部分,此为 可维护;当这些字用完后,并不是就彻底没有价值了,它彻底能够在后来的印刷中重复使用,此乃 可复用;此诗若要加字,只需另刻字加入便可,这就是 可扩展;字的排列能够横排,也能够竖排,此是 灵活性好。

 

上述案列反应了OOP的优势,便可维护性高,扩展性强,复用性高! 这些特色很是适用于用户需求变化频繁的互联网应用程序,这是学习OOP的重要缘由

可是OOP设计的程序需涉及类与对象,相应的复杂度会提升!

并不是全部程序都须要较高的扩展性,例如系统内核,一旦编写完成,基本不会再修改,使用面向过程来设计则更适用

 

二.类与对象

类和对象是面向对象编程中最核心的两个概念

对象是什么

对象是特征与技能的结合体

如:演员张一山,姓名和职业是他的特征,演戏是他的行为,按照这样的定义,生活中处处都是对象

在程序中:

用变量来表示对象的特征,用函数表示对象的技能

将这些变量和函数结合在一块儿,造成一个总体,就是对象,这是面向对象的精髓所在!

对象的另外一种理解方式

变量的做用数存储数据,函数的做用数处理数据

对象是将数据与处理数据的函数绑定在一块儿

类是什么

类就是类型,类别,种类; 是一系列对象中类似特征与技能的结合体

在生活中是一种抽象概念,例如人类,是不具体的

如某个对象属于人类,能够经过类别,了解这个对象具有的特征和技能

反过来看类就是对象的模板,同一类的对象,具有相同的特征和行为

为何须要类

现实生活中,经过对对象的分析总结,获得类型;用类型来标识不一样对象之间的差别;

在程序中,一样用于标识不一样对象之间的差别

另外一个重要的功能是做为对象的模板,例如学生类,不管是哪一个学生都具有学习这个方法,既然是相同的就不必为每一个对象单独编写,学生类负责提供这些相同的方法;

OOP第一步要作的就是定义须要的类

三.建立类和对象

定义类

以学生类Student为例,在Python中,定义类经过class关键字:

 
 
 
 
 
 
 
 
class Student:
    pass
 

class后面紧接着是类名,即Student,遵循python编码规范,类名一般是大写开头的单词,多个单词时使用驼峰命名法

建立对象

建立对象也称之为实例化,定义好Student类后,就能够根据Student类建立出Student的实例,建立实例经过类名加上()实现:

 
 
 
xxxxxxxxxx
 
 
 
 
stu1 = Student()
print(stu1)
#输出 <__main__.Student object at 0x10b11d588>
print(Student)
#输出 <class '__main__.Student'>
 

根据输出能够看到

变量名stu1指向一个Student类的实例,0x10b11d588是实例的内存地址,每一个实例的地址都不相同,

Student自己则是一个类(class)

对象的属性操做

对象是特征(属性)与行为(方法)的结合体

stu这个对象目前不具有任属性和方法,要为其添加属性能够在建立对象后使用点语法(变量名加 . )好比为stu对象添加name属性

 
 
 
xxxxxxxxxx
 
 
 
 
stu1.name = "Jerry"
 

一样经过点语法来获取对象的属性值

 
 
 
xxxxxxxxxx
 
 
 
 
print(stu1.name)
#输出 Jerry
 

四.初始化方法init

什么是初始化方法

用于为对象的属性设置初始值的函数

为何须要初始化方法

在类的实例(对象)中,一些属性是必须存在的,就可使用初始化函数来完成,好比Student对象中的name属性,它是必须的,用于惟一标识一个学生

尝试一下

 
 
 
xxxxxxxxxx
 
 
 
 
class Student:
    def __init__ (self,name):
        print("init run")
        self.name = name
 

执行过程:

在建立对象时Student("jack")会申请新的内存空间用于保存对象数据,接着自动调init函数

注意:

__init__函数要求第一个参数必须是self,该参数表示须要被初始化的对象自己,这样就能够将name属性绑定到对象上

能够将self改成其余任意的名称,但为了保证易读性一般是self,额外的参数须位于self以后

有了__init__方法,在建立实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不须要传,Python解释器本身会把实例变量传进去:

 
 
 
xxxxxxxxxx
 
 
 
 
# stu1 = Student()
# 以上代码将抛出异常:TypeError: __init__() missing 1 required positional argument: 'name'
stu1 = Student("jack")
# 输出 init run
print(stu1.name)
# 输出 jack
 

小结:

  • init函数用于为对象属性设置初始值
  • 在建立对象时会自动调用
  • 自动传入对象自己

五.属性查找顺序

能够将类中的内容都称之为属性,变量称为数据属性,函数就叫函数属性

类中能够声明变量来表示数据属性,为Student类添加数据属性和函数属性

 
 
 
xxxxxxxxxx
 
 
 
 
class Student:
    school = "Tsinghua" #数据属性
    
    def say_hello(self):#函数属性
        print("hello i am a student")
    
    def __init__ (self,name): #初始化函数
        self.name = name
   
 

也可使用点语法在建立对象后为对象增长数据属性

 
 
 
xxxxxxxxxx
 
 
 
 
stu = Student("Maria")
stu.age = 20
 

问题1:在类中声明的数据属性和建立对象后为增长的数据属性,有什么区别?

类中的数据属性是全部对象共享的

建立对象后为增长的数据属性,是这个对象特有的,去其余对象无关

问题2:类中声明的数据属性和建立对象后为增长的数据属性,其访问属性是怎样的?

优先查找对象本身的名称空间,若是没有则在类中找,若是类中也没有则到父类中找,直到找到为止,若是父类中也没有则抛出异常

!!!注意!!!

此处父类可能也有父类,会一直沿着继承关系查找到最终的父类Object,该继承关系,后续会详细讨论!

案列:网页中折叠此处

stu1 = Student("Jack")
stu2 = Student("Rose")

#1.类中的数据属性是全部对象共享的
print(stu1.school)
print(stu2.school)
#输出 Tsinghua
#输出 Tsinghua

#2.类中的数据属性访问的是同一块内存
print(id(stu1.school))
print(id(stu2.school))
#输出 4470412656
#输出 4470412656

#3.类的函数属性是绑定给对象使用的,bound method称为绑定方法,每一个对象的绑定方法内存地址不同
print(stu1.say_hello)
print(stu2.say_hello)
#输出 <bound method Student.say_hello of <__main__.Student object at 0x10cc405f8>>
#输出 <bound method Student.say_hello of <__main__.Student object at 0x10cc40630>>


#4.优先访问对象本身的名称空间 
# 修改stu1的学习属性为北京大学 会在自stu1的名称空间增长school属性
stu1.school = "Beijing"
print(stu1.__dict__)
print(stu2.__dict__)
#输出 {'name': 'Jack', 'school': 'Beijing'}
#输出 {'name': 'Rose'}

#4.1再次查看学校属性
print(stu1.school)
print(stu2.school)
#输出 Beijing
#输出 Tsinghua

#__dict__用于访问对象的名称空间 本质是一个字典类型数据,存储名称与值的映射关系

属性查找顺序:对象->类->父类

六.绑定方法与非绑定方法

什么是方法?

先理清方法,函数,技能的关系:

生活中对象的技能在程序中用函数表示

函数在面向对象中称之为方法,换种称呼而已!

如此说来,绑定方法也就是绑定函数

10为何要绑定?

在使用面向对象以前,数据与处理数据的函数是独立的没有任何联系,在调用函数时须要手动传入参数,若是要处理的数据有不少,参数的传递就是一个很是麻烦的事情,

原始的处理方式:函数 传参

问题1 调用函数时传入参数,若是要处理的数据有不少,编写了不少重复的代码,代码的阅读性不好

问题2 后期若是每次处理的数据个数变多了,函数须要修改参数列表,致使之前写的全部代码都须要修改,扩展性很是差

问题3 若是传入了错误的数据,好比须要整型却传入了字符串,形成函数没法正常工做

绑定方法的处理方式:

1.调用方法时传入对象,对象中包含了须要的全部数据,减小重复代码

2.后期数据变化时,修改类对象中的属性,方法中增长相应的处理代码,而方法参数不会发生变化,提升了扩展性

3.方法与对象进行绑定,没有对象则没法使用方法,而且在建立对象的初始化方法中,已经肯定了各个属性数据时正确的,如此一来避免了传入使用错误数据执行函数形成的问题

简单的说,就是将数据与处理数据的函数绑定在一块儿,没有数据则根本不须要处理数据的函数,反过来要执行处理数据的函数则必须提供要被处理的数据

类中定义的函数分红两大类

一:绑定方法

1.绑定到对象的方法:没有被任何装饰器装饰的方法。

在类中定义的函数默认都是绑定到对象的方法

特色:参数的第一个必须是self 表示当前对象自己,使用对象来调用,调用时会自动传入对象

2.绑定到类的方法:用classmethod装饰器装饰的方法。

特色:参数的第一个必须是cls表示当前类自己,使用类名来调用,调用时会自动传入类

二:非绑定方法:用staticmethod装饰器装饰的方法

特色:不与类或对象绑定,类和对象均可以调用,可是没有自动传值那么一说。就是一个普通函数

不过因为做用域在类中因此须要使用类或对象类调用

class Student:
    school = "Tsinghua" 
    
    def say_hello(self):# 绑定到对象的方法
        print(self)
        print("hello i am a student my name is %s" % self.name)
    
    def __init__ (self,name): #绑定到对象的方法
        self.name = name
    @classmethod     # 绑定到类的方法
    def school_info(cls):
        print(cls)
        print("the student from %s" % cls.school)
    
        
stu1 = Student("Jack")
print(stu1)
#输出 <__main__.Student object at 0x1063112e8>

#1.调用对象绑定方法
stu1.say_hello()
#输出 <__main__.Student object at 0x1063112e8>
#输出 hello i am a student my name is Jack

#查看对象绑定方法
print(stu1.say_hello)
#输出 <bound method Student.say_hello of <__main__.Student object at 0x10552b2e8>>
#含义 这个绑定方法是Student类中的say_hello函数,绑定到地址为0x10552b2e8的Student对象

#绑定方法本质上也是函数 只要能找到它就能调用它因此你能够这样来调用它 
Student.say_hello(stu1)
#输出 <__main__.Student object at 0x103818390>
#输出 hello i am a student my name is Jack


print(Student)
#输出 <class '__main__.Student'>
#2.调用类绑定方法
Student.school_info()
#输出 <class '__main__.Student'>
#输出 the student from Tsinghua

#查看类绑定方法
print(Student.school_info)
#输出 <bound method Student.school_info of <class '__main__.Student'>>
#含义 这个绑定方法是Student类中的school_info函数,绑定到Student这个类

绑定到类的方法与绑定到对象的方法总结

异同点:

相同

绑定对象调用时都有自动传参的效果

绑定到谁给谁就由谁来调用

不一样

绑定到类的方法自动传入当前类

绑定到对象的方法自动传入当前对象

另外:

绑定方法中的self 和 cls参数名 是能够随意修改的,可是self和cls是约定俗成的写法,为了提升可读性不建议修改

练习

1.建立Student类

2.拥有如下属性: 姓名 性别 年龄 学校 班级

3.拥有如下方法

save(name) 其做用是将这个对象序列化到文件中

get_obj(name)其做用是根据name从文件中反序列化为获得一个对象

分析save方法和get_obj 应该做为绑定给对象仍是绑定给类

七.对象之间交互练习

需求设计王者荣耀中的英雄类,每一个英雄对象能够对其余英雄对象使用技能

具有如下属性

英雄名称,等级,血量

和Q_hurt,W_hurt,E_hurt 三个属性,表示各技能的伤害量

具有如下技能

Q W E

三个技能都须要一个敌方英雄做为参数,当敌方血量小于等于0时角色死亡

代码实现:

 

八.OOP三大特性之封装

1.什么是封装

封装指的是隐藏对象的属性和实现细节,仅对外公开接口,控制程序中属性的访问权限;

python中的权限分为两种

1.公开 外界能够直接访问和修改

2.私有 外界不能直接访问和修改,在当前类中能够直接修改和访问

2.为何须要封装

一.封装属性

对于属性而言,封装就为了限制属性的访问和修改,其目的是为了保护数据安全

例如:

学生对象拥有,姓名,性别,年龄,和身份证号,分数;其中身份证是一个相对隐私的数据,不该该让外界访问到;

分数属性,是一个很是关键的数据,决定学员能不能正常毕业,不该被随意修改;

二.封装方法

一个大的功能不少状况下是由不少个小功能组合而成的,而这些内部的小功能对于用户而言是没有意义的,因此封装方法的目的是为了隔离复杂度;

例如:

电脑的开机功能,内部须要启动BIOS,读取系统配置,启动硬盘,载入操做系统,等等一系列复杂的操做,可是用户不须要关心这些实现逻辑,只要按下开机键等待开机便可;

3.如何封装

在属性名前添加两个下划线__,将其设置为私有的

1.封装数据属性实例:网页中折叠

class Student:
    def __init__(self,name,gender,age,id,score): # 初始化函数
        self.name = name
        self.gender = gender
        self.age = age
        self.__id = id  # 将id设置为私有的
        self.__score = score # 将score设置为私有的
    def test(self):
        print(self.__id)
        print(self.__score)

        
stu = Student("Jack","man",20,"320684198901010001",780)
#1.访问私有属性测试
#print(stu.id) #	直接访问到隐私数据
#print(stu.__id) # 换种写法
#以上两行代码均输出类似的错误 
#Traceback (most recent call last):
#  File "/Users/jerry/PycharmProjects/备课/写课件/test.py", line 102, in <module>
#    print(stu.id)
#AttributeError: 'Student' object has no attribute 'id'
#错误含义 在Student类的对象中没有一个id或__id属性

#2.修改私有属性测试
stu.score = 1 #	直接修改私有属性 因为语法特色,至关于给stu对象增长score属性
stu.__score = 2 #	直接修改私有属性 因为语法特色,至关于给stu对象增长__score属性
print(stu.score)
print(stu.__score)
#输出 1 
#输出 2

#看起来已经被修改了 调用函数来查看私有属性是否修改为功
stu.test()
#输出 320684198901010001
#输出 780
# 私有的数据没有被修改过

思考:封装能够明确地区份内外,封装的属性能够直接在内部使用,而不能被外部直接使用,然而定义属性的目的终归是要用,外部要想用类隐藏的属性,须要为其提供接口,让外部可以间接地使用到隐藏起来的属性,那这么作的意义何在?

答:能够在接口附加上对该数据操做的限制,以此完成对数据属性操做的严格控制。

class Teacher:
    def __init__(self,name,age):
        # self.__name=name
        # self.__age=age
        self.set_info(name,age)

    def tell_info(self):
        print('姓名:%s,年龄:%s' %(self.__name,self.__age))
    def set_info(self,name,age):
        if not isinstance(name,str):
            raise TypeError('姓名必须是字符串类型')
        if not isinstance(age,int):
            raise TypeError('年龄必须是整型')
        self.__name=name
        self.__age=age


t=Teacher('egon',18)
t.tell_info()

t.set_info('egon',19)
t.tell_info()

2.封装函数属性实例:网页中折叠

#取款是功能,而这个功能有不少功能组成:插卡、密码认证、输入金额、打印帐单、取钱
#对使用者来讲,只须要知道取款这个功能便可,其他功能咱们均可以隐藏起来
#这么作即隔离了复杂度,同时也提高了安全性
class ATM:
    def __card(self):
        print('插卡')
    def __auth(self):
        print('用户认证')
    def __input(self):
        print('输入取款金额')
    def __print_bill(self):
        print('打印帐单')
    def __take_money(self):
        print('取款')

    def withdraw(self):
        self.__card()
        self.__auth()
        self.__input()
        self.__print_bill()
        self.__take_money()

a=ATM()
a.withdraw()

4.python封装实现原理

#其实这仅仅这是一种变形操做且仅仅只在类定义阶段发生变形
#类中全部双下划线开头的名称如__x都会在类定义时自动变造成:_类名__x的形式:

class A:
    __N=0 #类的数据属性就应该是共享的,可是语法上是能够把类的数据属性设置成私有的如__N,会变形为_A__N
    def __init__(self):
        self.__X=10 #变形为self._A__X
    def __foo(self): #变形为_A__foo
        print('from A')
    def bar(self):
        self.__foo() #只有在类内部才能够经过__foo的形式访问到.

#A._A__N是能够访问到的,
#这种,在外部是没法经过__x这个名字访问到。
a = A()
print(a.__dict__)
#输出 {'_A__X': 10}

#定义运行阶段的赋值操做
a.__Y = 1
print(a.__dict__)
#输出 {'_A__X': 10, '__Y': 1}    __y并无发生变形

"""
变形原理总结:
1.这种机制也并无真正意义上限制咱们从外部直接访问属性,知道了类名和属性名就能够拼出名字:_类名__属性,而后就能够访问了,如a._A__N,即这种操做并非严格意义上的限制外部访问,仅仅只是一种语法意义上的变形,主要用来限制外部的直接访问。
2.变形的过程只在类的定义时发生一次,在定义后的赋值操做,不会变形
"""

5.隐藏的函数不会被子类覆盖

 

6.封装之property

property是什么

property是一个装饰器,将一个方法假装成普通属性,其特殊之处在于,该方法会在修改属性值时自动执行

与之对应的是setter与deleter装饰器:

setter装饰的方法会在修改属性值时自动执行

deleter装饰的方法会在删除属性值自动执行

为何须要property

当咱们将一个属性设置为私有以后,就没法直接访问它们了,须要为其建立两个方法,一个用于访问,一个用于修改 ,可是对于使用者而言,私有的和普通都是属性,然而一个能够用点来访问,用等号来修改,另外一个却要调用函数来存取,这就违反了统一访问原则

使用property

class Foo:
    def __init__(self,val):
        self.__NAME=val #将全部的数据属性都隐藏起来

    @property
    def name(self):
        return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)

    @name.setter
    def name(self,value):
        if not isinstance(value,str):  #在设定值以前进行类型检查
            raise TypeError('%s must be str' %value)
        self.__NAME=value #经过类型检查后,将值value存放到真实的位置self.__NAME

    @name.deleter
    def name(self):
        raise TypeError('Can not delete')

f=Foo('Jack') 

print(f.name) # 访问property属性
#输出 Jack
f.name="Rose" # 修改property属性 抛出异常'TypeError: 10 must be str'

del f.name # 删除property属性 抛出异常'TypeError: Can not delete' 

总结:property的做用是避免使用普通属性和私有属性时的方式发生变化

 

九.OOP三大特性之继承

什么是继承

为何须要继承

存在继承后的属性查找顺序

派生

在子类重用父类方法

经典类与新式类

菱形继承的问题

mro列表

1.什么是继承

继承是一种关系,经过继承关系,一个对象能够直接使用另外一个对象拥有的内容,例如王思聪继承王建林,王思聪就可使用王健林拥有的财产!

被继承的一方称之为父,即王健林; 继承的一方称之为子,即王思聪

OOP继承描述的是两个类之间的关系,经过继承,一个类能够直接使用另外一个类中已定义的方法和属性;

被继承的称之为父类或基类,继承父类的类称之为子类;

在python3中建立类时必然继承另外一个类,若是没有显式的指定父类,则默认继承object类; object是根类 全部类都直接或间接的继承object

2.为何须要继承

1.减小代码重复

2.为多态提供必要的支持,(关于多态下节会详细讨论!)

3.使用继承:

在类名后面的括号中指定要继承的父类名称​class 类名(父类名):

案例:在选课系统中,有老师和学生两种角色,老师拥有姓名,性别,年龄,学生也拥有姓名,性别,年龄,使用面向对象编程思想,能够将老师和学生定义为两个为不一样的类

class Teacher:
    def __init__(self,name,gender,age):
        self.name = name
        self.gender = gender
        self.age = age
    def say_hi(self):
        print("hi my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
        
class Student:
    def __init__(self,name,gender,age):
        self.name = name
        self.gender = gender
        self.age = age
    def say_hi(self):
        print("hi my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
#建立两个对象
t1 = Teacher("Jack","man",20)
t1.say_hi()
s1 = Student("Maria","woman",20)
s1.say_hi()

两个类中的内容彻底一致,则能够经过继承来重用代码

class Teacher:
    def __init__(self,name,gender,age):
        self.name = name
        self.gender = gender
        self.age = age
    def say_hi(self):
        print("hi my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))

class Student(Teacher):  #指定Teacher类继承Student类
    pass

#建立两个对象
t1 = Teacher("Jack","man",20)
t1.say_hi()
s1 = Student("Maria","woman",20)
s1.say_hi()

4.继承与抽象

问题:

继承描述的是子类与父类之间的关系,在上面的例子中,Student继承Teacher完成了代码的重用,可是很明显老师类不是学生类的父类,学生类也不属于老师类,这样的继承关系在逻辑上是错误的;OOP的概念来自于现实世界,因此继承应当遵循现实世界的逻辑;

如今暂且不考虑逻辑错误,来看这样一个状况:

TeacherStudent因为存在相同的属性,为了减小重复代码,让两个逻辑上没有继承关系的类,产生了继承关系,若是后期Teacher类中增长了教学的方法,因为继承关系的存在,学生类也会拥有教学的方法,这是不合理的;

答:

应当将TeacherStudent中彻底相同的部分抽取出来,放到另外一个类中,并让Teacher与Student去继承它,这个类称之为公共父类 ,可是这个类与实际的业务需求是无关的在现实中也不实际存在,它的做用仅仅是存储相同代码以减小重复;这一过程咱们称之为抽象;

综上所述,正确思路是:先抽象在继承

# 抽取老师和学生的相同内容 造成一个新的类,做为它们的公共父类
class Person:
    def __init__(self,name,gender,age):
        self.name = name
        self.gender = gender
        self.age = age
    def say_hi(self):
        print("hi my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
class Teacher(Person):	#指定Teacher类继承Person类
    pass
class Student(Person):  #指定Student类继承Person类
    pass

#建立两个对象
t1 = Teacher("Jack","man",20)
t1.say_hi()
s1 = Student("Maria","woman",20)
s1.say_hi()

抽象最主要的做用是划分类别(能够隔离关注点,下降复杂度),每一个类之干本身的事情,多个类相同的事情交给父类来干

继承的另外一种使用场景:

在开发程序的过程当中,若是咱们定义了一个类A,而后又想新创建另一个类B,可是类B的大部份内容与类A的相同时

不须要从头开始写一个类B,这就用到了类的继承的概念。

经过继承的方式新建类B,让B继承A,B会‘遗传’A的全部属性(数据属性和函数属性),实现代码重用

用已经有的类创建一个新的类,这样就重用了已经有的软件中的一部分甚至大部分,大大节省了编程工做量,这就是常说的软件重用,不只能够重用本身的类,也能够继承别人的,好比标准库,来定制新的数据类型,大大缩短了软件开发周期,对大型软件开发来讲,意义重大

 

5.存在继承关系后的属性查找

一个类必然继承另外一个类,被继承的类也有可能继承了其余类,至关于C继承B,B又继承A

此时查找属性的顺序是:

对象自己的名称空间 - > 类的名称空间 -> 父类的名称空间 -> 父类的父类名称空间 ->...object类

会沿着继承关系一直日后查找,直到找到为止,因为object是全部类的根类,因此若是找不着最后都会查找object类!

class Foo:
    def f1(self):
        print('Foo.f1')

    def f2(self):
        print('Foo.f2')
        self.f1()

class Bar(Foo):
    def f1(self):
        print('Bar.f1')


b=Bar()
b.f1()
#输出 Bar.f1
b.f2()
#输出 Foo.f2

6.派生与覆盖

什么是派生

当父类提供的属性没法彻底知足子类的需求时,子类能够增长本身的属性或非法,或者覆盖父类已经存在的属性,此时子类称之为父类的派生类;

什么是覆盖

在子类中若是出现于父类相同的属性名称时,根据查找顺序,优先使用子类中的属性,这种行为也称为覆盖

Person类派生出来的Teacher

# 抽取老师和学生的相同内容 造成一个新的类,做为它们的公共父类
class Person:
    def __init__(self,name,gender,age):
        self.name = name
        self.gender = gender
        self.age = age
    def say_hi(self):
        print("my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
class Teacher(Person):	#指定Teacher类继承Person类
    # Teacher类从Person类中继承到了say_hi方法 可是,老师打招呼时应当说出本身的职业是老师,因此须要
    # 定义本身的不一样的实现方式
    def say_hi(self):
        print("hi i am a Teacher")
        #print("my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
        #上一行代码与父类中彻底相同,能够直接调用父类提供的方法
        Person.say_hi(self)
# 建立Teacher对象
t1 = Teacher("Jack","man",20)
t1.say_hi()
#输出 hi i am a Teacher
#     my name is Jack age is 20 gender is man

在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能须要重用父类中重名的那个函数功能,应该使用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,所以即使是self参数也要为其传值

 

7.子类中重用父类的方法

不少状况下 子类中的代码与父类中仅有小部分不一样,却不得不在子类定义新的方法,这时候能够在子类中调用父类已有的方法,来完成大部分工做,子类仅需编写一小部分与父类不一样的代码便可

在子类中有两种方式能够重用父类中的代码

1.使用类名直接调用 ,该方式与继承没有关系,即时没有继承关系,也能够调用

2.使用super()

class Vehicle: #定义交通工具类
     Country='China'
     def __init__(self,name,speed,load,power):
         self.name=name
         self.speed=speed
         self.load=load
         self.power=power

     def run(self):
         print('开动啦...')

class Subway(Vehicle): #地铁
    def __init__(self,name,speed,load,power,line):
        #super(Subway,self) 就至关于实例自己 在python3中super()等同于super(Subway,self)
        super().__init__(name,speed,load,power)
        self.line=line

    def run(self):
        print('地铁%s号线欢迎您' %self.line)
        super(Subway,self).run()

class Mobike(Vehicle):#摩拜单车
    pass

line13=Subway('中国地铁','180m/s','1000人/箱','电',13)
line13.run()

8.经典类与新式类

 

 

即便没有直接继承关系,super仍然会按照mro继续日后查找

而第一种方式明确指定了要到哪个类中去查找,找不到则直接抛出异常

#A没有继承B,可是A内super会基于C.mro()继续日后找
class A:
    def test(self):
        super().test()
class B:
    def test(self):
        print('from B')
class C(A,B):
    pass

c=C()
c.test() #打印结果:from B


print(C.mro())
#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

*当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。若是每一个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每一个方法也只会被调用一次(注意注意注意:使用super调用的全部属性,都是从MRO列表当前的位置日后找,千万不要经过看代码去找继承关系,必定要看MRO列表)

 

9.组合

软件重用的重要方式除了继承以外还有另一种方式,即:组合

组合指的是,在一个类中以另一个类的对象做为数据属性,称为类的组合

class Equip: #武器装备类
     def fire(self):
         print('release Fire skill')

class Riven: #英雄Riven的类,一个英雄须要有装备,于是须要组合Equip类
     camp='Noxus'
     def __init__(self,nickname):
         self.nickname=nickname
         self.equip=Equip() #用Equip类产生一个装备,赋值给实例的equip属性
r1=Riven('锐雯雯')
r1.equip.fire() #可使用组合的类产生的对象所持有的方法

组合与继承都是有效地利用已有类的资源的重要方式。可是两者的概念和使用场景皆不一样,

1.继承的方式

经过继承创建了派生类与基类之间的关系,它是一种'是'的关系,好比白马是马,人是动物。

当类之间有不少相同的功能,提取这些共同的功能作成基类,用继承比较好,好比老师是人,学生是人

2.组合的方式

用组合的方式创建了类与组合的类之间的关系,它是一种‘有’的关系,好比教授有生日,教授教python和linux课程,教授有学生s一、s二、s3...

 

10.继承实现的原理

1.继承顺序

在Java和C#中子类只能继承一个父类,而Python中子类能够同时继承多个父类,如A(B,C,D)

若是继承关系为非菱形结构,则会按照先找B这一条分支,而后再找C这一条分支,最后找D这一条分支的顺序直到找到咱们想要的属性

若是继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先

经典类查找顺序

新式类查找顺序

这里谈到的广度优先不是简单的从左到右,像图中标识的依然会按照深度一层一层上找,可是若是下一个要找的类与继承列表中的其余类存在相同父类(就像EF有共同父类G),则不会查找公共父类,这一次深度查找结束,开始下一条查找路径(C -> F),

2.继承实现原理

对于你定义的每个类,python经过一个算法算出一个查找顺序存放在(MRO)列表中,这个MRO列表就是一个简单的全部基类的线性顺序列表,例如:

F.mro() #等同于F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。 而这个MRO列表的构造是经过一个C3线性化算法来实现的。不须要深究这个算法的原理,它实际上就是合并全部父类的MRO列表并遵循以下三条准则: 1.子类会先于父类被检查 2.多个父类会根据它们在列表中的顺序被检查 3.若是对下一个类存在两个合法的选择,选择第一个父类

 

 

11.接口(了解)

什么是接口?

usb就是一种接口,电源插座也是接口,接口实际上是一套协议规范;

为何须要接口?

电脑提供USB接口,可使用任何遵循USB接口协议的设备,其余设备只要按照USB协议的要求来设计产品,就可以被电脑使用,而电脑根本不须要关心这个设备具体是如何实现功能的

1.让使用者无需关心对象的类是什么,只须要的知道这些对象都具有某些功能就能够了,这极大地下降了使用者的使用难度。

2.使得外部使用者能够不加区分的处理全部接口兼容的对象

2.1:就好象linux的泛文件概念同样,全部东西均可以当文件处理,没必要关心它是内存、磁盘、网络仍是屏幕(固然,对底层设计者,固然也能够区分出“字符设备”和“块设备”,而后作出针对性的设计:细致到什么程度,视需求而定)。

2.2:再好比:咱们有一个汽车接口,里面定义了汽车全部的功能,而后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,你们只须要学会了怎么开汽车,那么不管是本田,仍是奥迪,仍是大众咱们都会开了,开的时候根本无需关心我开的是哪一类车,操做手法(函数调用)都同样

接口的使用

在python中根本就没有一个叫作interface的关键字,若是非要去模仿接口的概念

1.能够借助第三方模块:

http://pypi.python.org/pypi/zope.interface

2.也可使用继承来间接的实现接口

继承的两种用途

一:继承基类的方法,而且作出本身的改变或者扩展(代码重用);

二:声明某个子类兼容于某基类,定义一个接口类(模仿java的Interface),接口类中定义了一些接口名(就是函数名)但并未实现具体的功能,子类继承接口类,而且实现接口中的功能

class IOInterface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。
    def read(self): #定接口函数read
        pass

    def write(self): #定义接口函数write
        pass


class Txt(Interface): #文本,具体实现read和write
    def read(self):
        print('文本数据的读取方法')

    def write(self):
        print('文本数据的读取方法')

class Sata(Interface): #磁盘,具体实现read和write
    def read(self):
        print('硬盘数据的读取方法')

    def write(self):
        print('硬盘数据的读取方法')

class Process(Interface):
    def read(self):
        print('进程数据的读取方法')

    def write(self):
        print('进程数据的读取方法')

上面的代码只是看起来像接口,可是子类彻底能够不用去实现接口,没有强制性的要求子类必须实现父类的方法,这就用到了抽象类

12.抽象类 挪到多态

什么是抽象类

什么叫作抽象?

不具体,不清晰的就是抽象的,当咱们知道某些对象具有一些功能,可是并不清楚这些功能是如何实现的,那对于咱们而言这个功能就是抽象的; 抽象类也同样,若是这个类中的方法是不具体(没有实现功能的代码)的抽象的,那么这个类也是抽象的;

抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化,且有存在没有实现的方法;

为何使用抽象类?

抽象类能够实现强制性要求子类必须实现父类声明的方法,这样一来只要一个类是这个抽象类的子类,那么他必然实现了抽象类中的方法,对于使用者而言,只要知道抽象类中的方法,就能够无差异的使用,这个抽象类的任何子类,大大下降了使用成本!

实战
#_*_coding:utf-8_*_
#一切皆文件
import abc #利用abc模块实现抽象类

class All_file(metaclass=abc.ABCMeta):
    all_type='file'
    @abc.abstractmethod #定义抽象方法,无需实现功能
    def read(self):
        '子类必须定义读功能'
        pass

    @abc.abstractmethod #定义抽象方法,无需实现功能
    def write(self):
        '子类必须定义写功能'
        pass

# class Txt(All_file):
#     pass
#
# t1=Txt() #报错,子类没有定义抽象方法

class Txt(All_file): #子类继承抽象类,可是必须定义read和write方法
    def read(self):
        print('文本数据的读取方法')

    def write(self):
        print('文本数据的读取方法')

class Sata(All_file): #子类继承抽象类,可是必须定义read和write方法
    def read(self):
        print('硬盘数据的读取方法')

    def write(self):
        print('硬盘数据的读取方法')

class Process(All_file): #子类继承抽象类,可是必须定义read和write方法
    def read(self):
        print('进程数据的读取方法')

    def write(self):
        print('进程数据的读取方法')

wenbenwenjian=Txt()

yingpanwenjian=Sata()

jinchengwenjian=Process()

#这样你们的使用方法时彻底一致的,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()

print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)

补充:

抽象类中既能够包含抽象方法也能够包含普通方法和属性!

这和接口不一样,接口仅仅是协议,因此接口中不该该包含任何具体的实现代码!

十.OOP三大特性之多态

1.什么是多态

多态指的是一类事物有多种形态

例如:

动物有多种形态:

人,狗,猪

在程序中多态指的是,不一样对象能够响应相同方法,并能够有本身不一样的实现方式

2.为何须要多态

案例分析:

import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
    @abc.abstractmethod
    def talk(self):
        pass

class People(Animal): #动物的形态之一:人
    def talk(self):
        print('say hello')

class Dog(Animal): #动物的形态之二:狗
    def talk(self):
        print('say wangwang')

class Pig(Animal): #动物的形态之三:猪
    def talk(self):
        print('say aoao')
        
peo=People()
dog=Dog()
pig=Pig()

#peo、dog、pig都是动物,只要是动物确定有talk方法
#因而咱们能够不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()

#更进一步,咱们能够定义一个统一的接口来使用
def func(obj):
    obj.talk()
func(peo)
func(dog)
func(pig)

经过上述案列能够直观的体会到多态的好处,而且它并非一个新的知识点,python默认就是支持多态的

那么多态的带来的好处是什么?

1.增长了程序的灵活性

  以不变应万变,不论对象变幻无穷,使用者都是同一种形式去调用,如func(animal)

2.增长了程序额可扩展性

  经过继承animal类建立了一个新的类,使用者无需更改本身的代码,仍是用func(animal)去调用  

class Cat(Animal): #动物的另一种形态:猫
    def talk(self):
        print('say miao')
def func(animal): #对于使用者来讲,本身的代码根本无需改动
    animal.talk()
cat1=Cat() #实例出一只猫
func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能
say miao
'''
这样咱们新增了一个形态Cat,由Cat类产生的实例cat1,使用者能够在彻底不须要修改本身代码的状况下。使用和人、狗、猪同样的方式调用cat1的talk方法,即func(cat1)
'''

继承一章中指出,继承为多态提供了不要的支持,全部的动物 cat dog pig 它们都要先继承Animal类,这样一来,才能保证,它们都能响应talk方法,不至于在调用时发生异常;

固然若是子类的设计者,彻底按照Animal中规定的内容去实现子类,即便没有继承关系的存在,使用者也同样能够像使用其余对象同样使用这个子类对象, 这须要设计者在设计实现类时更加谨慎!

3.鸭子类型

  Python崇尚鸭子类型,即‘若是看起来像、叫声像并且走起路来像鸭子,那么它就是鸭子’

python程序员一般根据这种标准来编写程序。例如,若是想编写现有对象的自定义版本,能够继承该对象

也能够建立一个外观和行为像,但与它无任何关系的全新对象,后者一般用于保存程序组件的松耦合度。

例1:利用标准库中定义的各类‘与文件相似’的对象,尽管这些对象的工做方式像文件,但他们没有继承内置文件对象的方法

#两者都像鸭子,两者看起来都像文件,于是就能够当文件同样去用
class TxtFile:
    def read(self):
        pass

    def write(self):
        pass

class DiskFile:
    def read(self):
        pass
    def write(self):
        pass

例2:其实你们一直在享受着多态性带来的好处,好比Python的序列类型有多种形态:字符串,列表,元组,多态性体现以下

#str,list,tuple都是序列类型
s=str('hello')
l=list([1,2,3])
t=tuple((4,5,6))

#咱们能够在不考虑三者类型的前提下使用s,l,t
s.__len__()
l.__len__()
t.__len__()

len(s)
len(l)
len(t)
相关文章
相关标签/搜索