面向对象-进阶篇
一、类的继承
什么是继承?
-继承是一种新建类的方式,新建的类称为子类,被继承的类称为父类
-继承的特性是: 子类会遗传父类的属性
-继承是类与类之间的关系
为什么要用继承?
-可以减少代码的冗余
对象的继承:
-Python中支持一个类同时继承多个类
class Parentl:
pass
class Parent2:
pass
class Sub1(Parentl,Parent2): # 类Sub1 继承了Parentl,Parent2两个类
pass
# 使用__bases__方法可以获取对象 继承的类
print(Sub1.__bases__) # (<class '__main__.Parentl'>, <class '__main__.Parent2'>)
# 在python3中如果没有继承任何类,则默认继承object类
print(Parentl.__bases__) # (<class 'object'>,)
继承的应用:
-牢记对象是特征与功能的集合体,拿选课系统举例:
class OldboyPeople:
'''由于学生和老师都是人,因此都有姓名、年龄、性别'''
school = 'oldboy'
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
class OldboyStudent(OldboyPeople):
def choose_course(self):
print('%s is choosing course' %self.name)
class OldboyTeacher(OldboyPeople):
def score(self,stu_obj,num):
print('%s is scoring' %self.name)
stu_obj.score = num
stu1 = OldboyStudent('cxk',98,'male')
tea1 = OldboyTeacher('zhao',98,'male')
print(stu1.school)
print(tea1.school)
print(stu1.__dict__)
print(tea1.__dict__)
stu1.choose_course()
tea1.score(stu1,99)
print(stu1.score)
属性查找顺序:
class Foo:
def f1(self):
print('Foo.f1')
def f2(self):
print('Foo.f2')
class Bar(Foo):
def f1(self):
print('Bar.f1')
# 对象查找属性的顺序:对象自己-->对象的类-->父类-->父类。。。
obj = Bar() # self是obj本身,即先找到Bar的f1()
obj.f2()
obj.f1()
菱形继承问题
新式类:继承object的类,python3中全是新式类
经典类:没有继承object的类,只有python2中有
在菱形继承的时候,新式类是广度优先(老祖宗最后找);经典类深度优先(一路找到底,再找旁边的)
经典类:不找多各类最后继承的同一个类,直接去找下一个父类,广度优先
新式类:不找多各类最后继承的同一个类,直接去找下一个父类,广度优先
class G(object):
# def test(self):
# print('from G')
pass
print(G.__bases__)
class E(G):
# def test(self):
# print('from E')
pass
class B(E):
# def test(self):
# print('from B')
pass
class F(G):
# def test(self):
# print('from F')
pass
class C(F):
# def test(self):
# print('from C')
pass
class D(G):
# def test(self):
# print('from D')
pass
class A(B, C, D):
def test(self):
print('from A')
obj = A()
obj.test() # 查找顺序 A->B->E-C-F-D->G-object
也可以通过mro()方法获取所有基类的线性继承列表
print(A.mro())
# [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]
for i in A.mro():
print(i)
# <class '__main__.A'>
<class '__main__.B'>
<class '__main__.E'>
<class '__main__.C'>
<class '__main__.F'>
<class '__main__.D'>
<class '__main__.G'>
<class 'object'>
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
二、类的派生
派生:子类中新定义的属性的这个过程叫做派生,并且需要记住子类在使用派生的属性时始终以自己的为准
派生方法一:(类调用)
指名道姓的使用 跟继承没有关系
class OldboyPeople:
"""由于学生和老师都是人,因此人都有姓名、年龄、性别"""
school = 'oldboy'
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
class OldboyStudent(OldboyPeople):
'''由于学生类没有独自的__init__()方法,因此不需要声明继承父类的__init__()方法,会自动继承'''
def choose_course(self):
print('%s is choose course' %self.name)
class OldboyTeacher(OldboyPeople):
'''由于老师类有独自的__init__()方法,因此需要声明继承父类的__init__()'''
def __init__(self,name,age,gender,level):
OldboyPeople.__init__(self,name,age,gender)
self.level = level # 派生
def score(self,stu_obj,num):
print("%s is scoring" % self.name)
stu_obj.score = num
stu1 = OldboyStudent('cxk',98,'famale')
tea1 = OldboyTeacher('nike',88,'famale',10)
print(stu1.__dict__)
print(tea1.__dict__)
派生方法二:通过super关键字
-跟继承有关系
-super()是一个特殊的对象
class OldboyPeople:
school = 'oldboy'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class OldboyStudent(OldboyPeople):
def __init__(self,name,age,sex,stu_id):
# super()会按照mro列表拿到父类对象
# 对象来调用绑定方法,不需要传递第一个参数(self)
super().__init__(name,age,sex)
# 经典类和新式类
# 经典类中必须这么写(py3中没有经典类),都用上面的方法写
# super(OldboyStudent,OldboyPeople).__init(name,age,sex)
self.stu_id = stu_id
def choose_couse(self):
print('%s is choosing course' % self.name)
stu1 = OldboyStudent('cxk',98,'famale',1)
print(stu1.__dict__)
三、super()方法的使用
回顾一下绑定方法:
class Student:
def __init__(self,name,age):
self.name = name
self.age = age
def study(self):
print(self.name)
print('study...')
def chang_name(self,new_name):
print('原来的名字是%s'% self.name)
self.name = new_name
print('修改的名字是%s' % self.name)
类来调用对象的绑定方法(写在类中的函数,没加装饰器),有几个参数就传几个参数
Student.__init__(125,'cxk',78)
类实例化产生对象,会自动调用__init__完成初始化操作
stu = Student('cxk',78)
对象的绑定方法的特殊之处,会把对象本身当做第一个参数传入
stu.study()
stu2 = Student('tank',28)
stu2.study()
修改学生姓名
stu = Student('nick',48)
方式一
print(stu.name)
stu.name = 'tank'
print(stu.name)
方式二
stu.chang_name('张全蛋')
print(stu.name)
方式三
Student.chang_name(stu,'蔡徐坤')
print(stu.name)
方式四
定义一个函数
def chang_name(obj,name):
# 修改Obj对象的name属性
print('原来的名字是%s' %obj.name)
obj.name = name
print('修改的名字是%s' % obj.name)
chang_name(stu,'老王')
print(stu.name)
-object写与不写,在py3中没有区别
-有的人在py3中这么写,为了向下兼容
-调用父类方法的第一种方式:指名道姓的方式,跟继承关系无关
super()的方式:
class Person(object):
def __init__(self,name,age):
self.name = name
self.age = age
class Student(Person):
school = 'yyyy'
def __init__(self,name,age,course):
# super()相当于得到了一个特殊对象,第一个参数(self)不需要传值,调用绑定方法,会把自己传过去
# super().__init__(name,age)
# 看到别人这么写:super(类名,对象) py3中为了兼容py2
# 在py3中这么写和省略写法完全一样
# 在py2 中必须super(Student,self)写
super(Student,self).__init__(name,age)
self.course = course
stu= Student('cxk',78,'python')
print(stu.name)
print(stu.age)
print(stu.course)
总结:
-有继承关系的时候通常用super
-指名道姓的方式在什么情况下用呢?
1.没有继承关系
2.如果继承了多个父类,super是按照mro列表找,现在想知名道姓的用某个方法,就需要指名道姓的使用。
super练习:
super是按照mro列表找的
class A:
def f1(self):
print('A.f1')
class B:
def f1(self):
print('B.f1')
def f2(self):
print('B.f2')
super().f1()
class C(B,A):
def f1(self):
print('C.f1')
c= C()
c.f2()
print(c.f2())
print(C.mro())
四、组合
什么是组合?
对象的某个属性是另一个类的对象
组合的概念:
class Foo:
def __init__(self,bar):
self.bar=bar
class Bar:
pass
bar=Bar()
f=Foo(Bar())
f=Foo(bar)
print(f.bar)
使用组合可以减少代码冗余:
如何使用组合
例1:
class Person:
school = 'oldboy'
class Teacher(Person):
def __init__(self,name,age,level,course):
self.name=name
self.age=age
self.level=level
#course是课程对象,表示老师教授的课程
self.course=course
class Student(Person):
def __init__(self,name,age,course):
self.name=name
self.age=age
# course是课程对象,表示学生选的课程
self.course = course
class Course:
def __init__(self,course_name,course_price,course_period):
self.name=course_name
self.price=course_price
self.period=course_period
course=Course('Python',20180,7)
stu=Student('nick',19,course)
teacher=Teacher('nick',19,'高级',course)
#查看老师教授的课程名
print(teacher.course.name)
例2:
class Person:
school = 'oldboy'
class Teacher(Person):
def __init__(self,name,age,level,course):
self.name=name
self.age=age
self.level=level
#course是课程对象,表示老师教授的课程
self.course=course
class Student(Person):
# course=[] #错误
def __init__(self,name,age):
self.name=name
self.age=age
# course是课程对象,表示学生选的课程
self.course_list = [] # 定义一个空列表存放未来要添加的课程属性
def choose_course(self,course):
# self.course=[] #错误
#把课程对象追加到学生选课的列表中
self.course_list.append(course)
def tell_all_course(self):
#循环学生选课列表,每次拿出一个课程对象
for course in self.course_list:
#课程对象.name 取到课程名字
print(course.name)
class Course:
def __init__(self,course_name,course_price,course_period):
self.name=course_name
self.price=course_price
self.period=course_period
course=Course('Python',20199,7) # 生成课程对象
stu1=Student('nick',19)
stu1.choose_course(course) # 添加课程属性到stu1对象 # 对象的组合
stu2=Student('王二丫',19)
stu2.choose_course(course)
stu2.choose_course(Course('linux',19999,5)) # 再添加一个课程属性
查看stu1选的所有课程名称:
方法一(通过普通函数):
def tell_all_course(student):
for course in student.course_list:
print(course.name)
tell_all_course(stu1)
tell_all_course(stu2)
方法二(通过对象的绑定)
stu1.tell_all_course()
stu2.tell_all_course()
五、多态与多态性
什么是多态?
一类事物的多种形态 比如: 动物类: 猪,狗,人
多态性:
一种调用方式,不同的执行效果(多态性)
多态性是指在不考虑实例类型的情况下使用实例
好处:
1.增加了程序的灵活性: 以不变应万变,不论对象千变万化,使用者都是同一种形式去调用
2.增加了程序额可扩展性 :
多态基础:
class Animal:
def speak(self):
pass
class Pig(Animal):
def speak(self):
print('哼哼哼')
class Dog(Animal):
def speak(self):
print('汪汪')
class People(Animal):
def speak(self):
print('say hello')
pig=Pig()
dog=Dog()
people=People()
pig.speak()
dog.speak()
people.speak()
由上面可以看出动物下的各类都有同一个方法:
我们可以直接定义一个函数来执行类里面的方法
def animal_speak(obj): # obj就是对象
obj.speak()
animal_speak(pig)
animal_speak(people)
多态性的使用方法
第一种方式:用abc实现接口统一化,约束代码(用的比较少)
注意:如果使用了约束,但是没有在子类中使用约束的方法,在实例化产生对象的时候就会报错。
TypeError: Can't instantiate abstract class Func with abstract methods speak
import abc
#第一在括号中写metaclass=abc.ABCMeta
class Animal(metaclass=abc.ABCMeta):
#第二在要约束的方法上,写abc.abstractmethod装饰器
@abc.abstractmethod
def speak(self):
pass
class Pig(Animal):
def speak(self):
print('哼哼哼')
class Dog(Animal):
def speak(self):
print('汪汪')
class People(Animal):
def speak(self):
print('say hello')
people = People()
pig=Pig()
def animal_speak(obj):
obj.speak()
animal_speak(people)
第二种方式,用异常处理来实现(常用)
class Animal():
def speak(self):
#主动抛出异常
raise Exception('你得给我重写它啊')
class Pig(Animal):
def speak(self):
print('哼哼哼')
class People(Animal):
def speak(self):
print('say hello')
pig=Pig()
pe=People()
def animal_speak(obj):
obj.speak()
animal_speak(pig)
animal_speak(pe)
鸭子类型:只要走路像鸭子(对象与其他中有某个相同绑定方法),那你就是鸭子
class Pig:
def speak(self):
print('哼哼哼')
class People:
def speak(self):
print('say hello')
pig=Pig()
pe=People()
def animal_speak(obj):
obj.speak()
animal_speak(pig)
animal_speak(pe)
传统写法:
class File:
def read(self):
pass
def write(self):
pass
#内存类
class Memory(File):
def read(self):
print('Memory...read')
def write(self):
print('Memory...write')
class Network(File):
def read(self):
print('Network...read')
def write(self):
print('Network...write')
鸭子类型的写法:
# 内存类
class Memory:
def read(self):
print('Memory...read')
def write(self):
print('Memory...write')
class Network:
def read(self):
print('Network...read')
def write(self):
print('Network...write')
def read(obj):
obj.read()
m=Memory()
n=Network()
read(m)
read(n)
六、封装
封装是什么意思?
从封装本身的意思去理解,
封装就好像是拿来一个麻袋,把小猫,小狗,小王八,一起装进麻袋,然后把麻袋封上口子如何隐藏,把东西包装进去之后,隐藏起来,外部访问不到
如何用代码实现隐藏?
隐藏属性/隐藏方法 隐藏之后,外部访问不到,只有内部能够访问
隐藏属性:通过 __变量名来隐藏
隐藏方法:通过 __方法名来隐藏
隐藏属性是为了安全
把name 隐藏起来
class Person:
def __init__(self,name,age):
self.__name=name
self.__age=age
def get_name(self): # 自定义一个方法,让外部只能通过这种方来访问
# print(self.__name)
return '[----%s-----]'%self.__name
访问name.
p=Person('nick',89)
print(p.get_name())
隐藏的属性访问不到?实际上有方法能访问到
通过变形隐藏了属性
print(p._Person__name)
print(p.__dict__)
也可以隐藏方法,为了隔离复杂度
class Person:
def __init__(self,name,age):
self.__name=name
self.__age=age
def __speak(self):
print('6666')
p=Person('nick',89)
p.__speak() # 这样访问不到
print(Person.__dict__)
p._Person__speak()
什么时候属性变形,只要再类内部,以__变量名 命名的变量,都会被隐藏,会发生的变形,在外部放入的 __变量名 属性是不隐藏的:
class Person:
def __init__(self,name,age):
self.__name=name
self.__age=age
def set_xx(self,xx):
self.__xx=xx
p=Person('nick',18)
p.set_xx('6688')
print(p.__dict__)
类的property特性
计算人的bmi指数
property装饰器:把方法包装成数据属性,在使用时可以不用加括号而直接使用
class Person:
def __init__(self,name,height,weight):
self.name=name
self.height=height
self.weight=weight
@property
def bmi(self):
return self.weight/(self.height**2)
# return self.weight/(self.height*self.height)
p=Person('lqz',1.82,70)
# print(p.bmi()) # 如果不用property装饰器,就加要括号调用
print(p.bmi)
# p.name='ppp'
# p.bmi=90
property之setter和deleter
class Person:
def __init__(self,name,height,weight):
self.__name=name
self.__height=height
self.__weight=weight
@property
def name(self):
return '[我的名字是:%s]'%self.__name
#用property装饰的方法名.setter
@name.setter
def name(self,new_name):
# if not isinstance(new_name,str):
if type(new_name) is not str:
raise Exception('改不了')
if new_name.startswith('sb'):
raise Exception('不能以sb开头')
self.__name=new_name
print(f'成功修改为{new_name}')
# 用property装饰的方法名.deleter
@name.deleter
def name(self):
# raise Exception('不能删')
print('删除成功')
# del self.__name
p=Person('lqz',1.82,70)
# print(p.name)
# p.name='pppp'
# p.name='xxx'
#改不了,直接抛一异常
# p.name=999
# p.name='sb_nick'
# print(p.name)
del p.name
print(p.name)
七、绑定方法与非绑定方法:
绑定方法分为 对象的绑定方法 和 类的绑定方法
绑定方法的特殊之处: 绑定给谁就是谁来调用,并且会把自身传过来
类的绑定方法: 绑定给类,类来调用,会把类自身传进来
类的绑定方法用在什么地方?
不需要通过对象,只需要通过类就能获取到一些东西的时候,用类的绑定方法
class Person:
def __init__(self,name,age):
print(self)
self.name=name
self.age=age
# @classmethod
# def test(cls):
# print(cls)
# print('类的绑定方法')
# #类实例化产生对象,返回
# return cls('lqz',19)
@classmethod
def get_obj_by_name(cls,name):
#1 去文件中查找名字为 name 的pickle文件
#2 反序列化成对象
#3 return 对象
pass
def get_obj_by_name(self,name):
# 1 去文件中查找名字为 name 的pickle文件
# 2 反序列化成对象
# 3 return 对象
pass
per1=Person.test()
per2=Person('nick',18)
admin=Person.get_obj_by_name('lqz')
admin.create()
类的绑定方法,可以由对象来调
class Person:
def __init__(self,name,age):
# print(self)
self.name=name
self.age=age
@classmethod
def test(cls):
print(cls)
print('类的绑定方法')
# Person.test()
p=Person('nick',18)
#对象可以调用类的绑定方法,也是把该对象的类传入
p.test()
总结:
- classmethod 是个装饰器,放在类中函数的上面,该函数就变成了类的绑定方法
- 类的绑定方法由类来调用,自动把类传过去(对象也可以调,一般不用)
- 类的绑定方法用在什么地方?
- 不需要通过对象,只需要通过类就能完成某些事的时候,就把该方法绑定到类。
-
非绑定方法:
staticmethod 也是一个装饰器,装饰在类中函数上面,相当于普通方法,谁也不绑定对象、类都可以调用,但是不会自动传值。
class Person: def __init__(self, name, age): self.name = name self.age = age def object_method(self): print('我是对象绑定方法,对象来调用我') @classmethod def class_method(cls): print('我是类的绑定方法,类来调用我') #当成一个普通函数,只不过是写在类内部的 @staticmethod def static_method(): print('我是静态方法,谁都不绑定') #静态方法(非绑定方法) #类来调用 # Person.static_method() # 对象来调用 # p=Person('nick',19) # p.static_method()
生成一个唯一的id号。
import uuid print(uuid.uuid4())
静态方法(非绑定方法)的使用:
跟类和对象都没有关系的时候,可以定义成静态方法,一般在类内部使用,类外部也可以使用
它就是一个普通函数,想把它拿到类中管理,就可以定义成静态方法
class Person:
def __init__(self,name, age):
self.id=self.get_uuid()
self.name = name
self.age = age
#当成一个普通函数,只不过是写在类内部的
@staticmethod
def static_method():
print('我是静态方法,谁都不绑定')
@staticmethod
def get_uuid():
import uuid
return uuid.uuid4()
# import uuid
# def get_uuid():
# return uuid.uuid4()
# a=uuid.uuid4()
# p=Person(uuid.uuid4(),'nick',18)
# p=Person(get_uuid(),'nick',18)
p=Person('nick',19)
# print(p.id)
# print(p.get_uuid())
print(Person.get_uuid())
#面向对象高级:Person类也是一个特殊的对象