Python20-Day07
面向对象之继承与派生
什么是继承?
继承是一种创建新类的方式,新建的类可以继承一个或者多个父类,父类又称为基类或者超类,新建的类称为派生类或者子类
子类会‘遗传’父类的特性,从而解决代码重用问题
python中分为单继承和多继承
class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass1(ParentClass1): #单继承,父类是ParentClass1,子类是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass
查看继承
#print(SubClass1.__bases__) (<class '__main__.ParentClass1'>,) #print(SubClass2.__bases__) (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
经典类与新式类
1、只有python2中才分为经典类和新式类,python3中都是新式类
2、在python2中没有标明继承object类的类,以及该类的子类,都称为经典类
3、在python2中标明继承object类的类,以及该类的子类,称为新式类
4、python3中,无论是否继承object类,默认都继承object类,即python3中都为新式类
继承与抽象
继承描述的是子类与父类之间的关系,是什么是什么的关系,要找出这种关系,必须先抽象再继承
属性查找
class Foo: def f1(self): print('Foo.f1') def f2(self): print('Foo.f2') self.f1() class Bar(Foo): def f1(self): print('Foo.f1') b=Bar() b.f2()
#得到的结果为:
Foo.f2
Foo.f1
派生
子类也可以添加自己新的属性或者自己重新定义属性。一旦重新定义了自己的属性且与父类重名,调用新增的属性时,就以自己的为准了。
class ParentClass: def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex def tell_info(self): print('%s is in old_class!' %self.name) class SubClass(ParentClass): def __init__(self,name,age,sex): ParentClass.__init__(self,name,age,sex) # def tell_info(self): # print('%s is in subclass!' %self.name) stu1 = SubClass('zly',25,'female') stu1.tell_info() #此时调用结果为: zly is in old_class! #如果在SubClass子类中也定义tell_info属性,此时调用结果为: zly is in subclass!
组合与重用性
软件重用的重要方式除了继承之外还有另外一种:组合
组合指的是:在一个类中以另外一个类的对象作为数据属性,称为类的组合
继承:
通过继承建立了派生类与基类之间的关系,是一种什么是什么的关系。
当类之间有很多相同的功能,提取这些共同的功能做成父类。用继承比较好
组合:
用组合的方式建立了类与组合的类之间的关系,是一种有的关系。
class People: def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex class Course: def __init__(self,name,period,price): self.name=name self.period=period self.price=price def tell_info(self): print('<%s %s %s>' %(self.name,self.period,self.price)) class Teacher(People): def __init__(self,name,age,sex,job_title): People.__init__(self,name,age,sex) self.job_title=job_title self.course=[] self.students=[] class Student(People): def __init__(self,name,age,sex): People.__init__(self,name,age,sex) self.course=[] egon=Teacher('egon',18,'male','沙河霸道金牌讲师') s1=Student('牛榴弹',18,'female') python=Course('python','3mons',3000.0) linux=Course('python','3mons',3000.0) #为老师egon和学生s1添加课程 egon.course.append(python) egon.course.append(linux) s1.course.append(python) #为老师egon添加学生s1 egon.students.append(s1) #使用 for obj in egon.course: obj.tell_info()
抽象类
抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
从设计的角度看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象来的。
抽象类中只有抽象方法,没有实现功能,该类不能被实例化,只能被继承,且子类必须实现抽象方法。
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' #一切皆文件 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)
继承实现的原理
1、继承顺序
如果继承关系为非菱形结果,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支,
如果继承关系为菱形结构,属性查找:深度优先和广度优先。
2、继承原理
定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表。
所有父类的MRO列表遵循以下3条原则:
1、子类会先与父类被检查
2、多个父类会根据他们在列表中的顺序被检查
3、如果对下一个类存在两个合法的选择,选择第一个父类
面向对象之封装
如何隐藏?
在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这个名字访问到。
变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形
在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有。
#正常情况 >>> class A: ... def fa(self): ... print('from A') ... def test(self): ... self.fa() ... >>> class B(A): ... def fa(self): ... print('from B') ... >>> b=B() >>> b.test() from B #把fa定义成私有的,即__fa >>> class A: ... def __fa(self): #在定义时就变形为_A__fa ... print('from A') ... def test(self): ... self.__fa() #只会与自己所在的类为准,即调用_A__fa ... >>> class B(A): ... def __fa(self): ... print('from B') ... >>> b=B() >>> b.test() from A
封装数据:
将数据隐藏起来,然后对外提供操作该数据的接口,然后可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格限制。
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()
封装方法:目的是隔离复杂度
#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱 #对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做 #隔离了复杂度,同时也提升了安全性 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()
特性(property)
什么是特性property?
property是一种特殊的属性,访问它时会执行一段功能,然后返回值。
class People: def __init__(self,name,weight,height): self.name=name self.weight=weight self.height=height @property def bmi(self): return self.weight / (self.height**2) p1=People('egon',75,1.85) print(p1.bmi)
为什么要用property?
将一个类的函数定义成特性后,对象再去使用的时候obj.name,根本无法察觉自己的nameshi执行了一个函数后计算出来的。这种特性的使用方式遵循了统一访问的原则
面向对象之绑定方法与非绑定方法
类中定义的函数分成两大类
1、绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入)
1、绑定到类的方法:用classmethod装饰器装饰的方法
为类量身定制
2、绑定到对象的方法:没有被任何装饰器装饰的方法
为对象量身定制
属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值一说。
2、非绑定方法:用staicmethod装饰器装饰的方法
1、不与类或对象绑定,类和对象都可以调用。但是没有自动传值一说,就是一个普通工具。
绑定方法:
绑定给类的方法(classmethod)
classmethod是给类用的,即绑定到类,类在使用时会将类本身当做参数传给类方法的第一个参数,python内置了函数classmethod来吧类中的函数定义成类方法
#settings.py HOST='127.0.0.1' PORT=3306 DB_PATH=r'C:\Users\Administrator\PycharmProjects\test\面向对象编程\test1\db'
import settings class MySQL: def __init__(self,host,port): self.host=host self.port=port @classmethod def from_conf(cls): print(cls) return cls(settings.HOST,settings.PORT) print(MySQL.from_conf) #<bound method MySQL.from_conf of <class '__main__.MySQL'>> conn=MySQL.from_conf() conn.from_conf() #对象也可以调用,但是默认传的第一个参数仍然是类
非绑定方法
在类内部用staicmethod装饰的函数即非绑定方法,就是普通函数
staicmethod不与类或对象绑定,谁都可以调用,没有自动传值效果
import hashlib import time class MySQL: def __init__(self,host,port): self.id=self.create_id() self.host=host self.port=port @staticmethod def create_id(): #就是一个普通工具 m=hashlib.md5(str(time.time()).encode('utf-8')) return m.hexdigest() print(MySQL.create_id) #<function MySQL.create_id at 0x0000000001E6B9D8> #查看结果为普通函数 conn=MySQL('127.0.0.1',3306) print(conn.create_id) #<function MySQL.create_id at 0x00000000026FB9D8> #查看结果为普通函数
classmethod与staicmethod区别
import settings class MySQL: def __init__(self,host,port): self.host=host self.port=port @staticmethod def from_conf(): return MySQL(settings.HOST,settings.PORT) # @classmethod #哪个类来调用,就将哪个类当做第一个参数传入 # def from_conf(cls): # return cls(settings.HOST,settings.PORT) def __str__(self): return '就不告诉你' class Mariadb(MySQL): def __str__(self): return '<%s:%s>' %(self.host,self.port) m=Mariadb.from_conf() print(m) #我们的意图是想触发Mariadb.__str__,但是结果触发了MySQL.__str__的执行,打印就不告诉你: