继承与派生
什么是继承
是一种新建类的方式,新建的类称为子类,子类会遗传父亲的属性,可以减少代码冗余 在python当中,子类(派生类)可以继承一个或多个父类(基类,超类)
在python3中定义类时不写父类默认会继承object 在python2中需要写object 所以,为了兼容,在定义类时可以写上默认的object
class Parent1(object): #python3是不需要写上(object)的,但其实是有的
pass
class Parent2:
pass
class Sub1(Parent1):
pass
class Sub2(Parent1,Parent2): #继承多个父类
pass
print(Sub1.__bases__) #查看有哪些父类
print(Sub2.__bases__)
经典类与新式类
在Python2中类分为两种: 1、经典类:指的就是没有继承object类的类,以及该类的子类 2、新式类:指的就是继承object类的类,以及该类的子类 在Python3中统一都为新式类
继承可以实现代码重用
派生
当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
注意: 在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值
继承、派生示例
class OldboyPeople:
school = 'Oldboy'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def tell_info(self):
print('<名字: %s 姓名: %s 性别: %s>' %(self.name,self.age,self.sex))
class OldboyStudent(OldboyPeople):
def learn(self):
print('%s is learning' % self.name)
def tell_info(self): #派生
print('我是学生:',end='')
print('<名字:%s 年龄:%s 性别:%s>' %(self.name,self.age,self.sex))
class OldboyTeacher(OldboyPeople):
def teach(self):
print('%s is teaching' %self.name)
def tell_info(self): #派生
print('我是老师:',end='')
print('<名字:%s 年龄:%s 性别:%s>' % (self.name, self.age, self.sex))
stu1=OldboyStudent('dzm',18,'male')
teacher1=OldboyTeacher('egon',18,'male')
teacher1.tell_info() #子类中有tell_info则使用子类中的tell_info
属性查找
先在对象自己的名称空间查找,再在所属的子类名称空间查找,再在子类的父类的名称空间中查找
class Foo:
def f1(self):
print('Foo.f1')
def f2(self): #self=obj
print('Foo.f2')
self.f1() #obj.f1()
class Bar(Foo):
def f1(self):
print('Bar.f1')
obj=Bar()
print(obj.__dict__)
obj.f2()
结果:
{}
Foo.f2
Bar.f1
子类重用父类的功能之直接指定
此时就与调用普通函数无异了,因此即便是self参数也要为其传值
示例2:
class OldboyPeople:
school = 'Oldboy'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def tell_info(self):
print('<名字:%s 年龄:%s 性别:%s>' %(self.name,self.age,self.sex))
class OldboyStudent(OldboyPeople):
def learn(self):
print('%s is learning' %self.name)
def tell_info(self):
print('我是学生:',end='')
#self.tell_info() #stu1.tell_info()
OldboyPeople.tell_info(self) #直接调用指定类的函数,不依赖继承关系
stu1=OldboyStudent('牛榴弹',18,'male')
stu1.tell_info()
结果:
我是学生:<名字:牛榴弹 年龄:18 性别:male>
示例2:
class OldboyPeople:
school = 'Oldboy'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def tell_info(self):
print('<名字:%s 年龄:%s 性别:%s>' %(self.name,self.age,self.sex))
class OldboyStudent(OldboyPeople):
def __init__(self,name,age,sex,course,stu_id):
# self.name=name
#self.age=age
#self.sex=sex
OldboyPeople.__init__(self,name,age,sex) #调用指定类的函数,不依赖继承关系
self.course=course
self.stu_id=stu_id
def learn(self):
print('%s is learning' %self.name)
def tell_info(self):
print('我是学生:',end='')
#self.tell_info() #stu1.tell_info()
OldboyPeople.tell_info(self)
stu1=OldboyStudent('牛榴弹',18,'male','Python',1)
stu1.tell_info()
组合
代码重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
组合示例1
class OldboyPeople:
school = 'Oldboy'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def tell_info(self):
print('<名字:%s 年龄:%s 性别:%s>' % (self.name, self.age, self.sex))
class OldboyStudent(OldboyPeople):
def __init__(self, name, age, sex, course, stu_id,):
OldboyPeople.__init__(self, name, age, sex)
self.course = course
self.stu_id = stu_id
def learn(self):
print('%s is learning' % self.name)
def tell_info(self):
print('我是学生:', end='')
#self.tell_info() #stu1.tell_info()
OldboyPeople.tell_info(self)
class OldboyTeacher(OldboyPeople):
def __init__(self, name, age, sex, level, salary):
OldboyPeople.__init__(self, name, age, sex)
self.level = level
self.salary = salary
def teach(self):
print('%s is teaching' % self.name)
def tell_info(self):
print('我是老师:', end='')
OldboyPeople.tell_info(self)
class Date: #专门定义一个日期类
def __init__(self,year,mon,day):
self.year = year
self.mon = mon
self.day = day
def tell_birth(self):
print('出生日期是:<%s-%s-%s>' % (self.year, self.mon, self.day))
学生有生日的属性,老师也有生日的属性,但学生和老师都不是生日,因此不符合什么是什么的继承关系 但学生和老师都有生日这个属性,可以理解为什么有什么的关系 这里使用组合,专门定义一个日期类,组合也可以减少代码重用
stu1 = OldboyStudent('牛榴弹', 18, 'male', 'Python', 1,) #实例化一个学生对象
date_obj1=Date(1983, 3, 11) #实例化一个日期对象
stu1.birth=date_obj1 #给stu1这个对象添加一个名为birth的属性,把上面的日期实例赋值给它
看一个赋值后的stu1.birth是什么
print(stu1.birth)
结果:
<__main__.Date object at 0x000001CEB43A2240>
名为birth的属性实际指向了date_obj1这个对象,也就是指向了它后面的Date这个类 这样就等于把两个类间接的组合到了一起
既然已经把date_obj1这个对象赋值给了stu1.birth,那我们可以借此调用Date类里面的函数
stu1.birth.tell_birth() #等于date_obj1.tell_birth()
结果:
出生日期是:<1983-3-11>
组合示例2
class OldboyPeople:
school = 'Oldboy'
def __init__(self, name, age, sex,date_obj):
self.name = name
self.age = age
self.sex = sex
self.birth = date_obj
def tell_info(self):
print('<名字:%s 年龄:%s 性别:%s>' % (self.name, self.age, self.sex))
class OldboyStudent(OldboyPeople):
def __init__(self, name, age, sex, stu_id,date_obj):
OldboyPeople.__init__(self, name, age, sex,date_obj)
self.courses=[] #学生有课程,初始为空,让学生自己选课
self.stu_id = stu_id
def learn(self):
print('%s is learning' % self.name)
def tell_info(self):
print('我是学生:', end='')
# self.tell_info() #stu1.tell_info()
OldboyPeople.tell_info(self)
class OldboyTeacher(OldboyPeople):
def __init__(self, name, age, sex, level, salary,date_obj):
OldboyPeople.__init__(self, name, age, sex,date_obj)
self.level = level
self.salary = salary
self.courses=[] #老师也有课程,初始为空,让老师自己选
def teach(self):
print('%s is teaching' % self.name)
def tell_info(self):
print('我是老师:', end='')
OldboyPeople.tell_info(self)
class OldboySale(OldboyPeople):
def __init__(self,name,age,sex,kpi,date_obj):
OldboyPeople.__init__(self,name,age,sex,date_obj)
self.kpi=kpi
def tell_info(self):
print('我是销售: ',end='')
OldboyPeople.tell_info(self)
class Date:
def __init__(self,year,mon,day):
self.year = year
self.mon = mon
self.day = day
def tell_birth(self):
print('出生日期是:<%s-%s-%s>' % (self.year, self.mon, self.day))
class Course: #新定义一个课程类
def __init__(self,name,price,period):
self.name=name
self.price=price
self.period=period
def tell_info(self):
print('课程详细信息:<%s,%s,%s>' %(self.name,self.price,self.period))
#实例化两个课程
Python=Course('python自动化养猪',3000,'3mon')
Linux=Course('大数据分析-linux',3000,'3mon')
#实例化一个日期对象
date_obj=Date(1993,3,13)
#初始化时直接设定生日属性,这里与示例1不同,但原理上一样使用组合
# teacher1=OldboyTeacher('egon',18,'male',100,3000,date_obj)
# teacher1.courses.append(Python)
# teacher1.courses.append(Linux)
#
# # print(teacher1.courses)
# for course in teacher1.courses:
# course.tell_info()
stu1=OldboyStudent('xxxx',28,'female',1,date_obj)
# print(stu1.courses)
stu1.courses.append(Python)
stu1.courses.append(Linux)
print(stu1.courses)
结果:
[<__main__.Course object at 0x0000024AD3683518>, <__main__.Course object at 0x0000024AD3683550>]
for course in stu1.courses: #使用for循环可以直接取出课程
course.tell_info()
结果:
课程详细信息:<python自动化养猪,3000,3mon>
课程详细信息:<大数据分析-linux,3000,3mon>
抽象类
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。 抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计 这里模拟linux上一切皆文件的概念
import abc
定义一个父类,定义读写两个函数
class File(metaclass=abc.ABCMeta): #指定一个类的原类,metaclass是类似创建类的模板
@abc.abstractmethod #调用一个装饰器,意味着继承这个父类的子类必须有这个函数名称
def read(self):
pass
@abc.abstractmethod
def write(self):
pass
class Disk(File): #磁盘子类
def read(self):
print('disk read')
def write(self):
print('disk write')
class Process(File): #进程子类
def read(self):
print('Process read')
def write(self):
print('Process write')
实例出两个对象
d=Disk()
p=Process()
在使用时无需关心到底是什么,使用方法都是一样的 这样大家都是被归一化了,也就是一切皆文件的思想
d.read()
d.write()
p.read()
p.write()
继承实现的原理
深度优先与广度优先
python如何实现的继承
对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表。
Python3提供了一个.mro方法,可以以列表形式显示出查找顺序
...........省略...........
f1=F()
print(F.mro())
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止
子类重用父类的功能之super
理解了继承原理,就可以看一下另一种子类重用父类功能的方法super() 当你使用super()函数时,Python会在MRO列表上继续搜索下一个类 两种子类重用父类功能的方法最好不要混用
super示例1
class OldboyPeople:
school = 'Oldboy'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def tell_info(self):
print('<名字:%s 年龄:%s 性别:%s>' %(self.name,self.age,self.sex))
class OldboyStudent(OldboyPeople):
def __init__(self,name,age,sex,course):
# OldboyPeople.__init__(self,name,age,sex) #这种方法指名道姓,与继承关系无关
super(OldboyStudent,self).__init__(name,age,sex)
self.course=course
def tell_info(self):
print('我是学生: ',end='')
# OldboyPeople.tell_info(self)
super(OldboyStudent,self).tell_info()
stu1=OldboyStudent('egon',18,'male','python')
# print(stu1.name,stu1.age,stu1.sex,stu1.course)
stu1.tell_info()
示例2
class Foo:
def f2(self):
print('====?>')
def f1(self):
print('Foo.f1')
super().f2()
class Bar:
def f2(self):
print('Bar f2')
class Sub(Foo,Bar):
pass
s=Sub()
# print(Sub.mro())
# [<class '__main__.Sub'>,
# <class '__main__.Foo'>,
# <class '__main__.Bar'>,
# <class 'object'>]
s.f1()
结果:
Foo.f1
Bar f2 #结果反映出调用是从MRO列表当前的位置往后找,而不是找它自己的
注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表
多态与多态性
多态
多态指的是一类事物有多种形态,比如 动物有多种形态:人,狗,猪
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(pig)
结果:
say hello
say aoao
为什么要用多态性(多态性的好处)
1.增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
2.增加了程序额可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
鸭子类型
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)
封装
如何隐藏
1、开头的属性只是一种语法意义上的变形,并不会真的限制外部的访问 2、这种变形只在类定义阶段发送一次,类定义之后再新增的开头的属性不会变形 3、这种隐藏只对外不对内,因为类内部定义的属性在类定义阶段统一发生变形
class Foo:
__N=1 #_Foo__N=1
def __init__(self,x,y):
self.x=x
self.__y=y #self._Foo__y=y
def __f1(self): #_Foo__f1
print('f1')
def f2(self):
print(self.__N,self.__y) #print(self._Foo__N,self._Foo__y)
在知道规则的情况下可以找到所需内容,可见并不是真正意义的隐藏
print(Foo._Foo__N)
print(Foo._Foo__f1)
结果:
1
<function Foo.__f1 at 0x000001FAB8468AE8>
obj=Foo(1,2)
print(obj.__dict__)
print(obj._Foo__y)
结果:
{'x': 1, '_Foo__y': 2}
2
obj=Foo(1,2)
obj.f2()
结果:
1 2
封装的真正意义
封装数据属性的目的:外部无法直接访问数据属性,类内部开放接口,然后可以在接口内严格控制对属性的增删改查操作
class People:
def __init__(self,name,age):
self.set_info(name,age)
def tell_info(self):
print("姓名 %s 年龄 %s" %(self.__name,self.__age))
def set_info(self,name,age):
if type(name) is not str:
raise TypeError('name must be str')
if type(age) is not int:
raise TypeError('age must be int')
self.__name = name
self.__age = age
p=People('dzm',18)
p.tell_info()
修改
p.set_info('DZM',20)
p.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()
d=ATM()
d.withdraw()
取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱 对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做 隔离了复杂度,同时也提升了安全性
封装之property
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
为什么要用property 将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的 这种特性的使用方式遵循了统一访问的原则
class People:
def __init__(self,name,age,height,weight):
self.name=name
self.age=age
self.height=height
self.weight=weight
@property
def bmi(self):
return self.weight / (self.height ** 2)
egon=People('egon',18,1.80,75)
egon.height=1.82
print(egon.bmi())
此时获取bmi要调取类中的函数,但bmi也应该是人的属性之一,像查看属性(egon.bmi)那样查看岂不更好?使用@property实现了这一点
print(egon.bmi)
class People:
def __init__(self,name,):
self.__name=name
@property #针对的是查看一个属性的行为会触发下面这个函数的执行
def name(self):
return self.__name
@name.setter #设置
def name(self,obj):
if type(obj) is not str:
raise TypeError('name must be str')
self.__name=obj
@name.deleter #删除
def name(self):
# del self.__name
raise PermissionError('不让删')
@name.setter和@name.deleter都是固定用法
egon=People('egon')
print(egon.name)
修改
egon.name='EGON' #使用了@name.setter就可以像修改属性那样操作了
egon.name=35357 #因为做了判断,所以此时会返回异常'name must be str'
print(egon.name)
del egon.name #使用了@name.deleter就可以像删除属性那样操作了
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明
(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
绑定方法与非绑定方法
这里以从文件传参来举例
import settings #从文件读取HOST和PORT
import hashlib
import time
class MySQL:
def __init__(self,host,port):
self.host=host
self.port=port
def func(self):
print('%s 说:你好啊我的天' %self.name)
#从文件传参数
@classmethod #当需要传进来的是类时,使用@classmethod
def from_conf(cls): #此时cls等于MySQL
return cls(settings.HOST,settings.PORT)
@staticmethod #非绑定方法
def create_id(n):
m=hashlib.md5()
m.update(str(time.clock()+n).encode('utf-8'))
return m.hexdigest()
提醒: 绑定方法:绑定给谁就应该由谁来调用,谁来调用就会把谁当做第一个参数自动传入
conn=MySQL.from_conf()
当函数体代码不需要类传进来也不需要对象自动传进来时,这种函数就定义成非绑定方法 此时我们来看看结果
print(MySQL.create_id)
print(conn.create_id)
结果:
<function MySQL.create_id at 0x0000013A42048E18>
<function MySQL.create_id at 0x0000013A42048E18>
使用@staticmethod后无论是对象调用还是类调用都显示为一个函数而已
使用非绑定方法不意味着不需要传参
print(MySQL.create_id(1))
print(conn.create_id(2))
结果:
85a3525bf7884cad61c0c2fea69f08ed
38dea5a04f7a37b41f69eb5c7b396787
内置函数补充
isinstance 判断类型
判断类型可以不用type了
l=list([])
print(type(l) is list)
print(isinstance(l,list))
判断l是不是list的实例 结果: True
issubclass
判断一个类是不是另一个类的子类
class Foo:
pass
class Bar(Foo):
pass
print(issubclass(Bar,Foo)) 检查Bar类是否是 Foo类的派生类(子类) 结果: True
面向对象进阶
反射
能够通过字符串映射到属性身上
class Foo:
def __init__(self,name):
self.name=name
def f1(self):
print('===>f1')
obj=Foo('egon')
正常调用属性
# obj.name #obj.__dict__['name']
hasattr 判断object中有没有一个name字符串对应的方法或属性
print(hasattr(obj,'name')) #等同于obj.name
print(hasattr(obj,'f1')) #等同于obj.f1
有就返回True
getattr
if hasattr(obj,'f1'): 判断obj下与没有f1这个属性或发放
f=getattr(obj,'f1') #f=obj.f1 有则拿到它,通过字符串的方式
f() #拿到一个内存地址,加括号直接执行
print(getattr(obj,'xxx',None)) #None是默认返回值,如果你不做判断直接获取的话,若没有则会报错,此时加个None,会返回None
setattr #设置(添加、修改)
setattr(obj,'x',1) #等同于obj.x=1
print(obj.__dict__) #检查一下是否有了
delattr #删除
del obj.name
delattr(obj,'name')
print(obj.__dict__)
使用示例1:
class FtpClient:
def __init__(self,host,port):
self.host=host
self.port=port
self.conn='xxx'
def interactie(self):
while True:
cmd=input('>>: ').strip()
if not cmd:continue
cmd_l=cmd.split()
print(cmd_l)
if hasattr(self,cmd_l[0]):
func=getattr(self,cmd_l[0])
func(cmd_l)
def get(self,cmd_l):
print('geting...',cmd_l)
def put(self,cmd_l):
print('putting....',cmd_l)
client=FtpClient('1.1.1.1',23)
client.interactie()
类的内置方法
str
内置方法是在某种行为发生的时候默认触发
class People:
def __init__(self,name,age):
self.name=name
self.age=age
def __str__(self):
return '<name:%s age:%s>' %(self.name,self.age)
# 在打印对象的时候不要打印内存地址,希望打印有用的东西
egon=People('egon',18)
print(egon) #print(egon.__str__())
结果:
<name:egon age:18>
这个__str__函数默认就有 在打印时触发 默认打印的就是内存地址
del
注:如果产生的对象仅仅只是python程序级别的(用户级),那么无需定义__del__ 如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了__del__
f=open('a.txt','w',encoding='utf-8')
f.read()
f.close()
class Foo:
def __del__(self):
print('del---->')
obj=Foo()
del obj
print('主')
结果:
del---->
主
class Mysql:
def __init__(self,host,port):
self.host=host
self.port=port
self.conn=Connect(host,port)
def __del__(self): #回收
self.conn.close() #会先回收这个链接(操作系统资源)
m=Mysql('1.1.1.1',3306)
m.conn.execute('select * from db.user;')
先回收操作系统资源再回收m这个对象