面向对象-进阶篇

一、类的继承

​ 什么是继承?
​ -继承是一种新建类的方式,新建的类称为子类,被继承的类称为父类
​ -继承的特性是: 子类会遗传父类的属性
​ -继承是类与类之间的关系

​ 为什么要用继承?
​ -可以减少代码的冗余

​ 对象的继承:
​ -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中有

在菱形继承的时候,新式类是广度优先(老祖宗最后找);经典类深度优先(一路找到底,再找旁边的)

经典类:不找多各类最后继承的同一个类,直接去找下一个父类,广度优先92-菱形继承问题-经典类.png?x-oss-process=style/watermark

新式类:不找多各类最后继承的同一个类,直接去找下一个父类,广度优先

92-菱形继承问题-新式类.png?x-oss-process=style/watermark

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 是个装饰器,放在类中函数的上面,该函数就变成了类的绑定方法
  • 类的绑定方法由类来调用,自动把类传过去(对象也可以调,一般不用)
  • 类的绑定方法用在什么地方?
  • 不需要通过对象,只需要通过类就能完成某些事的时候,就把该方法绑定到类。
  1. 非绑定方法:
    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类也是一个特殊的对象
posted @ 2019-09-14 23:52  tomjoy  阅读(179)  评论(0编辑  收藏  举报