疫情环境下的网络学习笔记 python 4.9

4.9

昨日回顾

  1. 整合:封装,面向对象三大特性之一,把数据跟功能装在一起封起来,得到一个小包裹

  2. 对封装到类里的属性可以隐藏,在属性前面+ __

    class Stu:
    	__x = 10
    	def __f1(self):
    		print(__x)
    
    • 注意,只有前面加__,才是隐藏,前后都加下划线不会变形
    • 这种隐藏只是在语法上的变形操作,变形成 _类名__属性名
    • 变形只在检测语法的时候发生以此,在这之后定义的属性不会变形。要隐藏属性,只能在class内部做
    • 这种隐藏对外不对内,在内部可以按原样 __属性名 访问到
  3. 接口:为外界提供访问被隐藏的属性的方法,可以加上流程控制和其他逻辑

  4. 隐藏的意义:为外部使用隔离了复杂度,只可以通过接口使用功能

今日内容

  1. property:装饰器,也是一种封装

  2. 继承

    python支持多继承,优缺点

    新式类与经典类

  3. 如何让找出继承关系:抽象

  4. 在继承背景下的属性查找顺序

  5. 继承的实现原理:

    MRO

    菱形问题

正课

property 装饰器

装饰器是在不修改被装饰对象源代码及调用方式的前提下为被装饰对象添加方法

property是一个用类去实现的装饰器

示例1

计算一个人的体质参数bmi,体重 / 身高平方

class People:
	def __init__(self,name,weight,height):
		self.name = name
		self.weight = weight
		self.height = height
	def bmi(self):
		return self.weight / (self.height ** 2)
	
obj1 = People('deimos',100,1.80)
print(obj1.bmi())
  • bmi是通过计算得到的
  • bmi不是在初始化的时候就写死的值,应该写在一个功能里,每次使用应该临时计算得到

然而,我们更倾向于把bmi当成成一个可以直接使用的数据,而不是一个方法,所以要把这个功能伪装成一个数据属性:property

property将绑定给对象的方法伪装成一个数据属性,调用的时候就不用加括号调用了

@property
def bmi(self):
	return self.weight / (self.height ** 2)

print(obj1.bmi)
# 可以像使用数据一样去使用方法,不用管类内部的操作
# 其实就是把方法return的结果变成数据输出

示例2

隐藏数据属性

class People:
    def __init__(self, name):
        self.__name = name

    def get_name(self):
        return self.__name
    
    def set_name(self, val):
        if type(val) is not str:
            print('必须是str')
            return
        self.__name = val
    
    def del_name(self):
        print('不让删除')

obj1 = People('deimos')
print(obj1.get_name())

对于使用者,改名字更倾向于直接改,直接访问,而不是使用一个改名字,访问名字的功能

print(obj1.name)
obj1.name = 'aaa'
  • 在get_name头顶加 @property 可以像使用数据一样,直接使用.get_name的数据

  •   # 在class中,使用 property() 函数,python给你定义好的查改删,property中,第一个参数对应获取,第二个参数对应设置,第三个参数对应删除,第四个参数对应注释
      name = property(get_name,set_name,del_name)
      
      obj1 = People('deimos')
      print(obj1.name)
      obj1.name = 'aaa'
      print(obj1.name)
      del obj1.name
    
  •   @name.setter
      @name.deleter
    

继承

继承是一种创建新类的方式,新建的类称为子类或派生类,被继承的类称为父类,新建的类可以继承多个父类

class Parent1:
	pass

class Parent2:
	pass

class Sub1(Parent1):
    pass

class Sub2(Parent1,parent2):
    pass
# 括号中放要继承的父类
print(Sub1.__bases__)
# 通过base可以看到继承的父类

经典类与新式类

  • python2中,继承object的字类,以及该字类的所有字类都是新式类;没有继承object类的子类以及它的所有字类都是经典类
  • python3中所有类都是新式类,默认就会继承object
  • object是一个内置的类,有一些内置的关于类的功能,定义的所有类都可以使用

继承的用处

类用于解决对象与对象之间的代码冗余问题

多个子类中重复的代码和重复使用的数据和功能,放到父类中,解决类与类之间代码冗余的问题

python中子类可以继承多个父类,因此可以到多个父类中访问属性

python的多继承

python独有的特性,别的编程语言都只能继承一个类,这么做有优点也有缺点

优点
- 最大限度解决代码冗余

缺点
- 违背人的思维逻辑:继承表达的是一种“是”什么的关系
- 代码可读性变差
- 不建议使用多继承,扩展性会变差,有可能会引发菱形问题
- 如果不可避免地要重用多个父类的属性,应该使用Mixins

如何实现继承

找出类的继承关系,需要先抽象,再继承。抽象即总结相似之处,总结对象之间的相似之处得到类,总结类与类之间的相似之处就可以得到父类

程序之中要反着来,要先写父类,再写子类

例:写学生类,老师类,再把他们绑定给一个父类,实例化

class OldboyPeople:
    school = 'oldboy'
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex

class Student(OldboyPeople):  # 继承OldboyPeople类
    # 初始化的时候,自己没有__init__方法,于是去父类找,找到init,完成初始化
    def choose_course(self):
        print('选课')
        
        
class Teacher(OldboyPeople):
    school = 'oldboy'
    def __init__(self,name,age,sex,salary,level):
        # 此时,这里用的init与父类不完全一样,不可以直接用,但是可以调用父类中的功能,初始化父类中有的参数
        # 指名道姓地跟父类要父类的init方法,调用类的方法相当于调用函数,没有绑定对象中的自动传self的功能了,所以要手动多加一个self
        OldboyPeople.__init__(self,name,age,sex)
        self.salary = salary
        self.level = level
        # 父类中没有的,仍可以独创,init可以这样嵌套用
    
    def score(self,score):
        pass
    
    
stu_obj = Student('deimos',21,'male') # 可以使用父类的初始化方法
tea_obj = Teacher('aaa',22,'male',3000,10)

老师类与学生类具有共同的学校属性,且具有共同的方法,于是基于继承解决类之间的冗余问题

如果子类与父类中有重名的属性,以自己为准:查找顺序,先找自己的,找不到再去父类找

在子类中调用父类的函数,注意:从类中调用方法,相当于调用一个函数,不会自动传self,所以要 OldboyPeople.__init__(self,name,age,sex)

单继承属性查找

单继承下的属性查找,先找自己,找不到就找父类

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')

obj = Bar()
obj.f2()  # 找Bar,没有,找Foo,找到f2,再找f1,又开始从Bar开始找,找到f1
# Foo f2
# Bar.f1

想让对象调用当前类下的方法:指定类或隐藏

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')

obj = Bar()
obj.f2()

继承的实现原理

菱形问题

如果A中有一个方法,B和/或C都重写了该方法,而D没有重写它,那么D继承的是哪个版本的方法:B的还是C的?

继承原理

定义每一个类,Python都会计算出一个方法解析顺序(MRO)列表,该MRO列表就是一个简单的所有基类的线性顺序列表

所有属性的查找都基于这个MRO列表,在MRO列表中从左到右寻找,可以通过类名.mro() 查看

[<class '__main__.Student'>, <class '__main__.OldboyPeople'>, <class 'object'>]

python2与python3中算出的MRO是不一样的

菱形继承问题

  • 非菱形的情况
    • 经典类与新式类相同,一个分支一个分支找下去,最后找object
  • 菱形情况
    • 经典类:深度优先查找,一条道走到底
    • 新式类:广度优先,一条道走到黑之前,找别的路,最后一步再去找所有分支的父类

总结

  1. 多继承仍然要用,但是要规避几点问题:
    • 继承结构不要过于复杂
    • 要在多继承的背景下,什么“是”什么的关系:使用Mixins
原文地址:https://www.cnblogs.com/telecasterfanclub/p/12666733.html