Day22 面向对象(继承封装)
面向对象有三大特性:封装,继承,多态
1.继承
1 .什么是继承: 继承是一种关系,再程序中,继承描述的就是类与类之间的关系,例如A继承了B,A就可以使用B已经存在的方法和属性,A就称为是B的子类,B也就是父类,也称为基类
2.继承的特点:继承的一方可以直接使用被继承一方已经有的方法和属性.可以重用已经有的代码.提高代码的重用性
3:继承语法格式:
class A: pass class B(A): # B类继承了A类 pass class C(B,A): # C类同时继承了B类与A类 pass
3.抽象. 再我们使用继承时不要盲目的去写要先进行抽象,抽象就是否将多个子类中相同的部分进行抽取,形成一个新的类,这个过程就时抽象
4.继承的使用流程: 1 先抽象再继承 2,继承一个已经存在的类,扩展或时修改原始的功能
5.属性的查找顺序: 先从对象自己本身找,然后再是自身所在类中,再找所在类的父类中查找.层层递进,最终层为Object,若找不到则报错
class A: num = 1 class B(A): num = 2 d = B() d.num = 3 print(d.num) # 3
class A: num = 1 class B(A): num = 2 d = B() print(d.num) # 2
class A: num = 1 class B(A): # num = 2 pass d = B() print(d.num) # 1
6.派生: 当一个子类中出现了与父类中不同的内容是,这个子类就称之为派生类,通常子类都会有一些新的代码.所以一般的子类都是派生类
7.覆盖: 当子类出现了和父类名称完全一致的属性或方法时.子类在定义这些相同的方法的时候就会父类的这些方法进行覆盖 也称为重写overrides
8.子类中访问父类的内容 通过super() 语法
方式1: super(当前类名称,self).你要调的父类的属性或方法 方式2: super().你要调的父类的属性或方法 方式3: # 与继承无关 类名称.你要调的父类的属性或方法(self)
注意: 当继承一个现有的类,并且覆盖了父类的__init__方法时,必须要在初始化方法的第一行调用父类的初始化方法,并传入父类所需的参数
9 : 练习,实现一个可以限制元素类型的列表
class MyList(list): def __init__(self): super().__init__() def append(self,name): if type(name) is str: print("不能是字符串") else: super().append(name) m = MyList() m.append('6666') print(m[0])
10: 组合: 组合是描述两个对象什么有什么的关系,当两个类之间没有太大的关系,完全不属于同类,如果我们使用继承就不符合正常逻辑了.这是我们又需要去访问令一个类中的方法或属性,这时候我们就可以使用组合,使用组合最终目的也是提高代码的复用性,降低代码的耦合度
class Phone: def __init__(self,kind,price): self.kind = kind self.price = price def call(self): print('拨号中') class Stu: def __init__(self,name,age,iphone): self.name = name self.age = age self.iphone = iphone def with_call(self): print('我要打电话') iphone = Phone('苹果',6000) s1 = Stu('小明',18,iphone) s1.iphone.call()
11.新式类与经典类
新式类:任何显式或隐式的继承了object的类就称为新式类,在python3中所有的类都是新式类
经典类: 没有继承object的类,只有在python2中出现
当出现菱形继承是. 都是先深度,当遇到相同父类时就广度 ,可使用类中__mro__方法查看查找顺序
class Init(object): def __init__(self, v): print("init") self.val = v class Add2(Init): def __init__(self, val): print("Add2") super(Add2, self).__init__(val) print(self.val) self.val += 2 class Mult(Init): def __init__(self, val): print("Mult") super(Mult,self).__init__(val) self.val *= 5 class HaHa(Init): def __init__(self, val): print("哈哈") super(HaHa, self).__init__(val) self.val /= 5 class Pro(Add2,Mult,HaHa): pass class Incr(Pro): def __init__(self, val): super(Incr, self).__init__(val) self.val+= 1 # Incr Pro Add2 Mult HaHa Init p = Incr(5) print(p.val) # Add2 Mult 哈哈 init 5.0 8.0 c = Add2(2) print(c.val) # Add2 init 2 4 print() print(Incr.__mro__) # (<class '__main__.Incr'>, <class '__main__.Pro'>, <class '__main__.Add2'>, <class '__main__.Mult'>, <class '__main__.HaHa'>, <class '__main__.Init'>, <class 'object'>)
13:习题:
# 程序员,拥有,姓名,性别,年龄,工资,和编程技能 # 项目经理必须有程序员晋升而来,拥有奖金,和管理技能 # 请使用面向对象来表达这种关系 # 选做需求,让程序员和项目经理都能调用save将对象序列化到文件 import pickle import os class BaseClass: # 我们希望可以将存取操作抽取出来.以降低程序的耦合度 def save(self): # 先判断系统是否由想要存放的文件夹 if not os.path.exists(self.__class__.__name__): # 如果没有此文件则创建 os.makedirs(self.__class__.__name__) path = os.path.join(self.__class__.__name__, self.name) with open(path, 'wb') as f: pickle.dump(self, f) f.flush() @classmethod def select(cls, name): path = os.path.join(cls.__name__, name) if os.path.exists(path): with open(path, 'rb', ) as f: return pickle.load(f) class Coder(BaseClass): def __init__(self, name, gender, age, balance): self.name = name self.gender = gender self.age = age self.balance = balance def program(self): print(f"我是{self.name},我会编程") class Pm(Coder, BaseClass): def __init__(self, name, gender, age, balance, prize): super().__init__(name, gender, age, balance) self.prize = prize def menage(self): print(f"我会管理") p = Pm('小明', '男', 18, 18000, 6000) p.save() code1 = Pm.select('小明') print(type(code1)) code1.menage()
2.封装
1. 封装 : 封装就是将复杂丑陋的隐私的细节隐藏到内部,也就是隐藏内部的实现细节,并提供给外部访问的接口
2. 封装的目的: 1. 为了保证关键数据的安全性 2.对外部隐藏实现细节,隔离复杂度
3. 封装的使用环境 1. 当一些数据不希望被外界直接修改时 2. 当有一些函数或属性不希望被外界使用的时候
4. 使用方法: 私用化: 在属性或方法前加双下划线 特点: 外界不能直接访问 内部依然可以使用
class A: def __init__(self,name, age): self.name = name self.__age = age # 私有属性 def __say(self): # 私有化方法 print("from __say") def test(self): # 在自身内可以直接访问私有属性与私有方法 print(self.__age,) self.__say() def say(self): print('from say') class B(A): pass a = B('小明',18) print(a.name) # 小明 print(a.age) # 报错.'B' object has no attribute 'age' a.__say() # 报错 'B' object has no attribute '__say' a.say() # from say a.test() # 18 from __say
5. 在外界访问私有的内容,属性与函数虽然被封装了,,但是有时候我们还是需要在外界访问.这时候我们就可以通过定义方法来完成对私有属性的修改与访问
class Person: def __init__(self, name, age, id_card): self.name = name self.age = age self.__id_card = id_card def get_id_card(self): return self.__id_card def alter_id_card(self, new_id_card): if len(new_id_card) == 18 or len(new_id_card) == 15: print("修改成功") self.__id_card = new_id_card else: print("错误") f = Person('a', 14, '11111111111') f.id_card = 2222222 # 在外界临时修改,重新定义,内部并没有修改 print(f.id_card) # 2222222 print(f.get_id_card()) # 在外部通过方法获取内置的私有方法 f.alter_id_card('222222222222222') # 在外部定义方法来修改私有属性 print(f.id_card) # 2222222 print(f.get_id_card()) # 222222222222222 内部修改成功
6.在通过方法来修改或访问属性,本身并没有是什么问题,但是这给对象的使用者带来了麻烦,使用者必须要知道那些是普通属性,哪些是私有属性,并且使用不同的方式法来调用他们 ,然后python 就给我们提供了3个有关的装饰器
@property 获取属性方法 2 @key,setter 修改属性方法 3 @key.deleter 删除属性方法 @property可以将私有属性伪装成普通属性,与普通属性的访问方法一致 key是被property装饰的方法名称,也就是属性的名称,内部会创建一个对象,变量名称就是函数名称 所以再是由setter与deleter时必须保证使用对象的名称去调用方法所以时key.setter
class Person: def __init__(self,name,age ,id_card): self.name = name self.age = age self.__id_card = id_card @property def id_card(self): return self.__id_card @id_card.setter def id_card(self, new_id_card): if len(new_id_card) == 18 or len(new_id_card) == 15: print("修改成功") self.__id_card = new_id_card else: print("错误") @id_card.deleter def id_card(self): print('不能删') f = Person('a',14,'11111111111') print(f.id_card) # 11111111111 f.id_card = "222222222222222" print(f.id_card) # 222222222222222 del f.id_card # 不能删
7.封装的原理: 就是再加载类的时候,把__方法或属性 替换成了_类名__方法或属性.... 封装的实现原理本质就时替换了变量名称
8.property 可以用来实现计算属性 计算属性的值不能直接获得,必须通过计算才能获得
# kg/m**2 class Person: def __init__(self,height,weight): self.height = height self.weight = weight @property def bmi(self): return self.weight/pow(self.height,2) jay = Person(170,60) print(jay.bmi) # 0.0020761245674740486
9.接口类:接口就是一组功能的集合,但是接口中仅包含功能的名字,不包含具体的实现代码.接口本质就时一套协议标准,遵循的对象就能被调用,接口的目的就时为了提高扩展性
class USB: def open(self): pass def close(self): pass def read(self): pass def write(self): pass class Mouse(USB): def open(self): print("鼠标开机.....") def close(self): print("鼠标关机了...") def read(self): print("获取了光标位置....") def write(self): print("鼠标不支持写入....") def pc(usb_device): usb_device.open() usb_device.read() usb_device.write() usb_device.close() m = Mouse() # 将鼠标传给电脑 pc(m) class KeyBoard(USB): def open(self): print("键盘开机.....") def close(self): print("键盘关机了...") def read(self): print("获取了按键字符....") def write(self): print("可以写入灯光颜色....") # 来了一个键盘对象 k = KeyBoard() pc(k)
10 抽象类 指的就是包含抽象方法,没有具体函数体方法的类.可以限制子类必须类中定义的抽象方法 导入abc模块
import abc class A(metaclass=abc.ABCMeta): @abc.abstractmethod def open(self): pass @abc.abstractmethod def close(self): pass @abc.abstractmethod def read(self): pass @abc.abstractmethod def write(self): pass class Mouse(A): def open(self): print("鼠标开机.....") def close(self): print("鼠标关机了...") def read(self): print("获取了光标位置....") def write(self): print("鼠标不支持写入....") def pc(usb_device): usb_device.open() usb_device.read() usb_device.write() usb_device.close() m = Mouse() # 将鼠标传给电脑 pc(m) class KeyBoard(A): def open(self): print("键盘开机.....") def close(self): print("键盘关机了...") def read(self): print("获取了按键字符....") def write(self): print("可以写入灯光颜色....") # 来了一个键盘对象 k = KeyBoard() pc(k)
总结:接口类就是一套协议规范,明确子类们应该具备哪些方法功能
抽象类就是导入abc模块,强制要求子类必须按照协议中规定来实现
然后,python不推崇限制语法,所以我们可以设计成为鸭子类型,就是让多个不同类对象具备相同的属性和方法