Python开发之路-面向对象三大特性之继承、多态、封装 以及反射、attr方法及授权
打印字典里的键值对,带上序号,且不带括号
dic = { 'a':1, 'b':2, 'c':3 } for i,v in enumerate(dic.items(),1): print(i,v[0],v[1])
或者
for i,v in enumerate(dic,1):
print(i,v,dic[v])
1.类的继承、派生
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Animal: def eat(self): print("%s 吃 " %self.name) def drink(self): print ("%s 喝 " %self.name) def shit(self): print ("%s 拉 " %self.name) def pee(self): print ("%s 撒 " %self.name) class Cat(Animal): def __init__(self, name): self.name = name self.breed = '猫' def cry(self): print('喵喵叫') class Dog(Animal): def __init__(self, name): self.name = name self.breed='狗' def cry(self): print('汪汪叫')
2.接口继承 import abc
可以限制其子类 子类若不实现 则无法实例化 实现了归一化设计
import abc class Fulei(metaclass=abc.ABCMeta): @abc.abstractmethod #--> 以下方法不用具体实现 但可以限制其子类 子类若不实现 则无法实例化 实现了归一化设计 def write(self): pass @abc.abstractmethod def read(self): pass
3.继承顺序
python2里分为经典类和新式类 经典类就是基类没有继承object的类 新式类是基类主动继承object的类
python3里没有继承的话,默认会继承一个object类 因此python3里的类都是新式类 采用广度优先的方法
Python的类如果继承了多个类,那么其寻找的方法有两种,分别是:深度优先(经典类采用 从左开始 找到头后 去右边找 找到基类的 个停止)和广度优先(新式类采用 从左找 到头的前一个停下 去找右边的)
终极解释:对于定义的每一个类,python会计算出一个方法解析数列表(MRO),这个MRO列表就是一个简单的所有基类的线形顺序表
可以通过__mro__方法查看继承顺序!!!!
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
4.子类中调用父类方法
!!super()调用 后期修改父类时,子类逻辑不受影响 默认将self传入参数
class Vehicle: # 定义交通工具类 Country = 'China' def __init__(self, name, speed, load, power): self.name = name self.speed = speed self.load = load self.power = power def run(self): print('开动啦...') class Subway(Vehicle): # 地铁 def __init__(self, name, speed, load, power, line): #Vehicle.__init__(self, name, speed, load, power) #在子类中调用父类的方法 self就是实例化的对象
#替换为
super().__init__(name,speed,load,power) self.line = line def run(self): print('地铁%s号线欢迎您' % self.line) #Vehicle.run(self)
#替换为
super().run()
line13 = Subway('中国地铁', '180m/s', '1000人/箱', '电', 13) line13.run()
此时输出结果为:
地铁13号线欢迎您
开动啦...
5.多态 依附于继承 反映在执行过程中
多态的概念指出了对象如何通过他们共同的属性和动作来操作以及访问,而不需考虑他们具体的类
多态表明了动态绑定的存在,允许重载及运行时类型确定和验证
class H2O: def __init__(self,name,temperature): self.name = name self.temperature = temperature def turn_obj(self): if self.temperature <0 : print('温度过低 %s 已结冰' %self.name) elif self.temperature >0 and self.temperature <100: print('%s 液化成水' %self.name) else: print('温度过高 %s 已被蒸发' %self.name) class Water(H2O): pass class Steam(H2O): pass class Ice(H2O): pass w1 = Water('水',25) s1 = Steam('水',105) i1 = Ice('水',-10)
水的三种形态都执行了相同的动作,具有多态性
此时输出的结果为:
水 液化成水
温度过高 水 已被蒸发
温度过低 水 已结冰
6.封装
封装也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏
第一个层面:类就是一个麻袋,本身就是一种封装
第二个层面:类中定义私有的,只在类的内部使用,外部无法访问
第三个层面:明确区分内外,内部的实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个访问连接 类的内部设置一个函数 内部调用隐藏的属性 则该方法称为接口函数或访问函数
class People: __star = 'earth' def __init__(self,name,age): self.name = name self.age = age def get_star(self): #-->此为接口函数 print(self.__star) p1 = People('chris',19) # print(p1._People__star) p1.get_star()
(1)单下划线开头 隐藏属性 要明确内外部 外部不可用 但这只是个约定 !!!python 并没有真正限制你 希望使用者不要调用
直接单下划线加属性名就可调用
(2)两个单下划线开头时 python内部会对属性进行重命名 前面加入单下划线再加上类名
要访问属性时 前面加上_+类名
class People: __star = 'earth' def __init__(self,name,age): self.name = name self.age = age p1 = People('chris',19) print(p1._People__star)
封装时一定要注意,没必要隐藏的数据千万不要隐藏!!!! 不然后期要开很多接口!!!
7.反射/自省 四个方法实现(hasattr(),getattr(),setattr(),delattr())
(1)hasattr(object,name) 用来判断object中有没有一个叫做name字符串对应的方法或属性 能否调用到
class People: star = 'earth' def __init__(self,name,age): self.__name = name self.age = age def get_star(self): print(self.star) p1 = People('chris',19) print(hasattr(p1,'star')) print(hasattr(p1,'get_star'))
此时返回值为True
(2)getattr(object,name,default=None) 获取属性和方法 相当于object.name
class People: star = 'earth' def __init__(self,name,age): self.__name = name self.age = age def get_star(self): print(self.star) p1 = People('chris',19) func = getattr(p1,'get_star',‘没有这个参数’) func()
此时执行了get_star方法,如果找不到,就会打印没有这个参数
(3)setattr(object,key,value) 也可以设置函数 用lambda
setattr(p1,'sb','joe') 等同于 p1.sb = 'joe'
(4)delattr(object,name) 相当于 del object.name
自省的好处:合作时可以实现可插拔式设计
if hasattr(p1,'post'): func = getattr(p1,'get') func() else: print('可以执行其他逻辑')
8.动态导入模块
m1 = __module__('day23.test') 会将字符串提取出来,找到模块 拿到的值是day23这个模块 既最顶层的模块
可以用importlib模块来实现:
import importlib
m1 = importlib.import_module('test.t')
要执行的话 需要 m1.test.test1() 来执行test下的test1函数
当用 from m1.test import * 此时不能导入被隐藏起来的属性 既用下划线封装的函数 但可以用下换线找到其本尊
9.__setattr__,__delattr__,__getattr__
(1)__getattr__ 当类的内部不存在你调用的属性时 会触发__getattr__的运行 !!!重点使用!!!
class Fu: star = 'qe' def __init__(self,name): self.name = name def __getattr__(self, item): print('您输入的属性【%s】不存在' %item) --> item就是要查找的属性aa f1 = Fu('joe') f1.aa
此时会打印: 您输入的属性aa不存在
(2)__delattr__ 在对象调用 del 删除属性时 会触发
def __delattr__(self, item): print('----> from delattr') # del self.item #无限递归了 self.__dict__.pop(item)
(3)__setattr__ 在设置值时触发 实例化时也会触发 因为实例化本身就是在设置值 self.key = value 的话会造成无限递归
class Fu: def __init__(self,name): self.name = name def __setattr__(self, key, value): print('chris') self.__dict__[key] = value ###这个不写的话 设置的值就不会加入到字典中 f1 = Fu(100) print(f1.__dict__)
可以用其实现类型检查: 设置和修改值时触发
class Fu: star = 'qe' def __init__(self,name): self.name = name def __setattr__(self, key, value): if type(value) is str: self.__dict__[key] = value else: print('设置值时出现错误,必须是字符串类型')
10.包装标准数据类型 定制自己的数据类型
class List(list): pass a1 = List('hello world') print(a1,type(a1))
此时Lish完完全全继承了类list 打印结果为 ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'] <class '__main__.List'> __main__表示当前文件下一个叫List的类
例子:
class List(list): def show_middle(self): mid_index = int(len(self)/2) return self[mid_index] def append(self,p_object): if type(p_object) is str: # list.append(self,p_object) --> 不推荐 可以用之前推荐的调用父类的方法 super().append(p_object) else: print('加入字典的类型必须是字符串!!!')
此处实现了传入列表中的值必须是字符串的操作
11.授权
授权:授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
实现授权的关键点就是覆盖__getattr__方法!!!
文件处理中进行授权:对write方法自定义:
import time class Wenjian: def __init__(self,filename,mode,encoding): self.jubing = open(filename,mode,encoding=encoding) #-->通过open方法在内部产生一个文件描述符 def __getattr__(self, item): return getattr(self.jubing,item) #-->在调用属性时触发 使其在文件描述符中获取用户想要的方法 并返回 def write(self, word): #--> 自定义文件描述符中的方法 t = time.strftime('%Y-%m-%d %T') self.jubing.write('%s %s\n' % (word, t)) p1 = Wenjian('b.txt','w','utf-8') p1.write('我是帅哥') p1.write('我是帅哥2') p1.write('我是帅哥3')