面向对象
1. 三大编程范式
- 面向过程编程
- 函数式编程
- 面向对象编程
2. 类与对象的概念
(1)类
- 类是一种数据结构,是一个抽象的概念
- 把一类事物的相同的特征和动作整合到一起就是类
- 是事物就要有属性,属性分为数据属性(就是变量)和函数属性(就是函数,在面向对象里通常称为方法)
- 类和对象均用点来访问自己的属性
- 经典类与新式类
- 只有python2中才分新式类与经典类,python3中统一都是新式类
- 新式类和经典类声明的最大不同在于,所有新式类必须继承至少一个父类
- 所有类不论是否显式声明父类,都有一个默认继承object父类
- 所有类不论是否显式声明父类,都有一个默认继承object父类
# 经典类 class 类名: pass # 新式类 class 类名(父类): pass # 在python3 中,上述两种定义方式全都是新式类
# 只能查看到属性名字 print(dir(类名)) # 查看类的属性字典 print(类名.__dict__) class Chinese: pass print(Chinese.__name__) # 类名 print(Chinese.__doc__) # 类的文档 print(Chinese.__base__) # 类的共同祖先 print(Chinese.__bases__) # 以元组的形式打印类的祖先 print(Chinese.__module__) # 显示类所在的模块 print(Chinese.__class__) # 显示类型
(2)对象
- 对象就是基于类而创建的一个具体的事物,也是特征和动作整合到一起
- 类与对象的关系:对象都是由类产生的
- 实例化:由类生产对象的过程叫实例化,类实例化的结果就是一个对象。实例只有数据属性,函数属性到类里去要。
# 在 python 中声明函数与声明类很相似 def functionName(args): '函数文档字符串' 函数体 ''' class 类名 '类的文档字符串' 类体 ''' # 创建一个类 class Chinese: '这是一个中国人的类' pass # 用类实例化出一个对象 p1 = Chinese() print(p1)
3. 面向对象设计/编程
- 面向对象设计(Object oriented design—OOD)
- 将一类具体事物的数据和动作整合到一起,即面向对象设计
- 不会特别要求面向对象编程语言,OOD可以由纯结构化语言来实现(如C)。但如果想要构造具备对象性质和特点的数据类型,就需要在程序上作更多的努力。
- 面向对象编程(Object-oriented programming—OOP)
- 用定义类 + 实例 / 对象的方式去实现面向对象的设计(使用class独特的语法来进行面向对象设计)
- 用面向对象语言写程序,和一个程序的设计是面向对象的,两者是两码事。
4. 静态属性、类方法、静态方法
- 静态属性:说的就是数据属性。在函数属性前加上@property。封装逻辑,让用户在调用时感知不到后端在执行什么样的逻辑,就像是在调用一个普通的数据属性一样。静态属性既可以访问实例,也可以访问类。
- 类方法:在函数属性前加上@classmethod。函数参数默认写成cls,可以访问类的属性,不能访问实例的属性。
- 静态方法:在函数属性前加上@staticmethod。静态方法只是名义上的归属类管理,不能访问类属性,也不能访问实例属性,只是类的工具包。
class Room: def __init__(self,name,owner,width,length,height): self.name = name self.owner = owner self.width = width self.length = length self.height = height @property def cal_area(self): return self.width * self.length r1 = Room('摘星楼','沈越',100,100,10000) r2 = Room('岳福居','段虹',300,200,50) print(r1.cal_area) print(r2.cal_area)
class Room: tag = 1 def __init__(self,name,owner,width,length,height): self.name = name self.owner = owner self.width = width self.length = length self.height = height @property def cal_area(self): return self.width * self.length @classmethod # 只能访问类的属性,实例也能用,但不那么用 def tell_info(cls): print(cls) print('-->',cls.tag) Room.tell_info()
# staticmethod 静态方法只是名义上的归属类管理,不能使用类变量和实例变量,是类的工具包 class Room: tag = 1 def __init__(self,name,owner,width,length,height): self.name = name self.owner = owner self.width = width self.length = length self.height = height @property def cal_area(self): return self.width * self.length @classmethod # 只能访问类的属性,实例也能用,但不那么用 def tell_info(cls): print(cls) print('-->',cls.tag) @staticmethod # 类的工具包,不和类榜单,也不和实例绑定 def bath(a,b,c): print('{},{},{}正在洗澡'.format(a,b,c)) def test(x,y): # 这样实例没法调用,需要把self传进去,这么写没有意义 print(x,y) Room.bath('tom','jack','andy') # tom,jack,andy正在洗澡 r1 = Room('摘星楼','沈越',100,100,10000) r1.bath('tom','jack','andy') # tom,jack,andy正在洗澡 Room.test(1,2) # r1.test(1,2) 报错
5.组合
- 组合:定义一个小轿车的类,它有发动机、车体、方向盘、轮胎等数据属性,这几个属性又可以是通过一个类实例化的对象,这就是组合。
- 用途:a.做类与类之间的关联(类与类之间没有共同点,但有关联,用组合实现);b.小的组成大的。
# 组合 class Hand: pass class Foot: pass class Trunk: pass class Head: pass class Person: def __init__(self,id_num,name,hand,foot,trunk,head): self.id_num = id_num self.name = name self.hand = Hand() self.foot = Foot() self.trunk = Trunk() self.head = Head()
class School: def __init__(self,name,addr): self.name = name self.addr = addr class Course: def __init__(self,name,price,period,school): self.name = name self.price = price self.period = period self.school = school s1 = School('QinL','Beijing') s2 = School('QinL','London') s3 = School('QinL','Paris') c1 = Course('Python',50000000,'1h',s1) c2 = Course('Linux',10000,'1h',s2) # 查看开设 python 的学校名是什么 print(c1.school.name) # QinL msg = ''' 1 QinL 北京校区 2 QinL 伦敦校区 3 QinL 巴黎校区 ''' while True: print(msg) menu = {'1':s1,'2':s2,'3':s3} choice = input('选择学校>>:') school_obj = menu[choice] name = input('课程名>>:') price = input('课程费用>>:') period = input('课程周期>>:') new_course = Course(name,price,period,school_obj) print('课程{}属于{}学校'.format(new_course.name,new_course.school.name))
6.继承
- python中类的继承分为:单继承和多继承
- 子类继承了父类的所有属性
- 子类自定的属性如果和父类重名了,那子类就在自己的属性字典里新增一条,找的时候先在自己那儿找
class ParentClass1: pass class ParentClass2: pass class SubClass(ParentClass1): # 单继承 pass class SubClass(ParentClass1, ParentClass2): # 多继承 pass
class Dad: '这个是爸爸类' money = 10 def __init__(self,name): print('爸爸') self.name = name def hit_son(self): print('{}正在打儿子'.format(self.name)) class Son(Dad): money = 1000 # print(Dad.__dict__) # print(Son.__dict__) s1 = Son('tom') print(s1.name) print(s1.money) print(Dad.money) s1.hit_son() # 爸爸 # tom # 1000 # 10 # tom正在打儿子
class Dad: '这个是爸爸类' money = 10 def __init__(self,name): print('爸爸') self.name = name def hit_son(self): print('{}正在打儿子'.format(self.name)) class Son(Dad): money = 1000 def __init__(self,name,age): self.name = name self.age = age def hit_son(self): print('来自子类') s1 = Son('tom',18) s1.hit_son() # 来自子类
(1)什么时候用继承?
- 当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
- 当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好
(2)继承的两种含义
- 含义一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)(这种方式少用)
- 含义二:声明某个子类兼容于某基类,定义一个接口类,子类继承接口类,并实现接口中定义的方法
实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
继承的第二种含义非常重要,它又叫“接口继承”。接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好像linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(对底层设计者,可以区分出“字符设备”和“块设备”,然后做出针对性的设计,细致到什么程度,视需求而定)。
import abc class All_file(metaclass=abc.ABCMeta): @abc.abstractmethod def read(self): pass @abc.abstractmethod def write(self): pass class Disk: def read(self): print('disk read') def write(self): print('disk write') class Cdrom: def read(self): print('cdrom read') def write(self): print('cdrom write') class Mem: def read(self): print('mem read') def write(self): print('mem write') m1 = Mem() m1.read() m1.write()
(3)继承顺序
- Python的类可以继承多个类,Java和C#中只能继承一个类
- Python的类如果继承了多个类,那么气寻找方法的方式有两种,分别是:深度优先(一直到基类没有再走另一条线)和广度优先(到基类前没找到,则先走另一条线,最都找基类)
- 多继承情况下,类是经典类时,按照深度优先方式查找;类是新式类时,按照广度优先方式查找。
- python3中所有类都是新式类,按照广度优先方式查找。
python到底是如何实现继承的,对于定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表。
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
- 子类会先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查
- 如果对下一个类存在两个合法的选择,选择第一个父类

class A: def test(self): print('A') class B(A): # def test(self): # print('B') pass class C(A): def test(self): print('C') class D(B): # def test(self): # print('D') pass class E(C): def test(self): print('E') class F(D,E): # def test(self): # print('F') pass # F 有test 方法,则找到自己的 # F 没有test 方法,先找到 D 的,D 也没有,先找到B 的,B 也没有,找到E 的 # 找的顺序 F-->D-->B-->E-->C-->A # 若最终都没有,报错:'F' object has no attribute 'test' f1 = F() f1.test() print(F.__mro__) # (<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, # <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
(4)子类中调用父类方法
子类继承了父类的方法,然后想基于原有基础上进行修改,那么就需要在子类中调用父类的方法。只有实例化时、对象调用类方法时,会自动传 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.line = line def show_info(self): print(self.name,self.speed,self.load,self.power,self.line) def run(self): Vehicle.run(self) print('{}{}号线开动啦'.format(self.name,self.line)) line13 = Subway('北京地铁','10km/s',10000000,'电',13) line13.show_info() line13.run() # 北京地铁 10km/s 10000000 电 13 # 开动啦 # 北京地铁13号线开动啦
使用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) super().__init__(name, speed, load, power) # super()相当于super(__class__,self) self.line = line def show_info(self): print(self.name,self.speed,self.load,self.power,self.line) def run(self): # Vehicle.run(self) super().run() print('{}{}号线开动啦'.format(self.name,self.line)) line13 = Subway('北京地铁','10km/s',10000000,'电',13) line13.show_info() line13.run() # 北京地铁 10km/s 10000000 电 13 # 开动啦 # 北京地铁13号线开动啦
7. 多态
- 多态的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类
- 多态表明了动态(运行时)绑定的存在,允许重载及运行时类型确定和验证
- 多态:由不同的类实例化得到的对象,调用同一个方法,执行的逻辑不同
- 多态实际上是依附于继承的两种含义的:“改变”和“扩展”本身就意味着必须有机制去自动选用你改变/扩展过的版本,故无多态,则两种含义就不可能实现。所以多态实质上是继承的实现细节。
class H2o: def __init__(self,name,temperature): self.name = name self.temperature = temperature def turn(self): if self.temperature < 0: print('[{}]温度太低结冰了'.format(self.name)) elif self.temperature > 0 and self.temperature < 100: print('[{}]液化成水'.format(self.name)) elif self.temperature > 100: print('[{}]温度太高变成了水蒸气'.format(self.name)) class Water(H2o): pass class Ice(H2o): pass class Steam(H2o): pass w1 = Water('水',25) i1 = Ice('冰',-20) s1 = Steam('蒸汽',3000) def func(obj): obj.turn() func(w1) func(i1) func(s1)
8. 封装
python不依赖语言特性去封装数据,而是通过遵循一定的数据属性和函数属性的命名约定来达到封的效果。
第一个层面的封装:任何以单下划线开头的名字都应该是内部的(类里面的,相对外部调用者来说),私有的
class People: _star = 'earth' def __init__(self,id,name,age,salary): self.id = id self.name =name self._age = age self._salary = salary def _get_id(self): print(f'我是私有方法啊,我找到的id是{self.id}') # 明明约定好了,只要属性前加一个单下划线,那它就属于内部的属性,不能被外部调用了,为什么还能调用? # 因为这只是一种约定,python并不会真的阻止你访问私有的属性 p1 = People('12345','tom',27,10000000) print(People._star) # earth
但实际上,其仍能被访问,这只是一种约定。python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么 from module import * 时不能被导入,但是 from module import _private_module 依然是可以导入的。
其实很多时候去调用一个模块的功能时会遇到单下划线开头的(如socket._socket, sys._home, sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部的你,一意孤行也是可以用的,但不应该这样用。
第二个层面的封装:双下划线开头的名字。双下划线开头的属性在继承给子类时,子类是无法覆盖的(原理也是基于python自动做了双下滑开头的名字的重命名工作)
class People: __star = 'earth' def __init__(self,id,name,age,salary): self.id = id self.name =name self._age = age self._salary = salary def _get_id(self): print(f'我是私有方法啊,我找到的id是{self.id}') p1 = People('12345','tom',27,10000000) # 直接访问双下划线命名的会报错,type object 'People' has no attribute '__star' # 因为python对双下划线开头的做了重命名的操作 # print(p1.__star) print(p1._People__star) # earth
class People: __star = 'earth' def __init__(self,id,name,age,salary): self.id = id self.name =name self._age = age self._salary = salary def _get_id(self): print(f'我是私有方法啊,我找到的id是{self.id}') # 访问函数、接口函数 def get_star(self): print(self.__star) p1 = People('12345','tom',27,10000000) # 直接访问双下划线命名报错 # p1.__star # 真正的封装,在内部可以使用,在外部调用不到 p1.get_star() # earth
第三个层面的封装:明确区分内外,内部实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个访问接口给外部使用(这才是真正的封装)。
封装绝不等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”。真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明(对外透明指,外部调用者可以顺利的得到自己想要的功能,完全意识不到内部细节的存在)。
上面提到了单下划线和双下划线来命名私有属性,那么哪种方式好呢?大多数情况,应该让非公共名称以单下划线开头。但是,如果你清楚你的代码会涉及到子类,并且有些内部属性应该在子类中隐藏起来,那么才考虑使用双下划线的方式。但无论哪种方式,其实python都没有从根本上限制你的访问。
在其他语言中私有的属性在外部就是不能被访问的,在python中则相反。面向对象有封装、多态、继承三大特性,这只是面向对象所支持的特性,一些人错误的认为,面向对象编程兼备了三大特性,那一定是好的,于是他们随意把属性定义成私有的,却又在需求变更时发现自己的定义不合理,又不得不通过某种方式把私有的属性公开。在python中的解决方法是定义一个访问函数,在内部去调用私有属性,但这是在问题出在滥用封装,后来利用访问函数去填坑。python并不严格限制外部对私有属性的访问,也许是为了避免滥用封装。
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者,而外部调用者也可以知道自己不可以碰哪里,只要接口这个基础约定不变,则代码改变不足为虑。
class Room: def __init__(self,name,width,length,high): self.name = name self.__width = width self.__length = length self.__high = high def tell_area(self): return self.__width * self.__length r1 = Room('卧室','tom',100,100,100) area = r1.tell_area() print(area)
9. 面向对象的优点
面向对象是一种更高等级的结构化编程方式,有两点好处:
- 通过封装明确了内外,类的实现者就考虑类内部的实现,类的调用者不用考虑具体的实现细节
- 通过继承+多态在语言层面支持了归一化设计
不用面向对象语言(即不用class),一样可以做归一化(如泛文件概念、游戏行业的一切皆精灵),一样可以封装(通过定义模块和接口),只是用面向对象语言可以直接用语言元素声明这些而已;而用了面向对象语言,满篇都是class,并不等于就有了归一化的设计。甚至,因为被这些花哨的东西迷惑,反而更不知道什么才是设计。
10. 反射
反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。python中四个可以实现自省的函数
- hasattr(object, name):判断object中有没有一个name字符串对应的方法或属性
- getattr(object, name, default=None):相当于在执行object.name,写了default,找不到也不会报错
- setattr(x, y, z):给对象设置属性,setattr(对象,key,value)
- delattr(x, y):删除属性
class BlackMedium: feture = 'Ugly' def __init__(self,name,addr): self.name = name self.addr = addr def sell_houce(self): print(f'{self.name}正在卖房子') def rent_houce(self): print(f'{self.name}正在租房子') b1 = BlackMedium('XX置地','天露园') hasattr(b1,'name') # 就是在检测b1能不能调用的到name这个属性 # b1.name--> b1.__dic__['name'] print(b1.__dict__) # {'name': 'XX置地', 'addr': '天露园'} print(hasattr(b1,'name')) # True print(hasattr(b1,'sell_houce')) # True print(getattr(b1,'name')) # XX置地 print(getattr(b1,'rent_houce')) # 函数地址 <bound method BlackMedium.rent_houce of <__main__.BlackMedium object at 0x00000224D6E466A0>> func = getattr(b1,'rent_houce') func() # XX置地正在租房子 # print(getattr(b1,'abc')) 没有会报错 print(getattr(b1,'abc','没有这个属性')) # 没有这个属性 setattr(b1, 'expensive', True) # b1.expensive = True setattr(b1, 'name', 'TT置地') print(b1.__dict__) # {'name': 'TT置地', 'addr': '天露园', 'expensive': True} delattr(b1,'expensive') # del b1.expensive print(b1.__dict__) # {'name': 'TT置地', 'addr': '天露园'} # setattr还可以加函数属性 setattr(b1,'func',lambda x:x+1) print(b1.__dict__) print(b1.func) print(b1.func(10)) # {'name': 'TT置地', 'addr': '天露园', 'func': <function <lambda> at 0x0000026D3ADC1E18>} # <function <lambda> at 0x0000026D3ADC1E18> # 11 setattr(b1,'func1',lambda self:self.name+'haha') print(b1.__dict__) print(b1.func1(b1)) # {'name': 'TT置地', 'addr': '天露园', 'func': <function <lambda> at 0x000002380E141E18>, 'func1': <function <lambda> at 0x000002380E603EA0>} # TT置地haha
以上四个示例,将object对象换为类,也可以执行。
类的内置attr属性
class Foo: x = 1 def __init__(self,y): self.y = y def __getattr__(self, item): print('执行__getattr__') def __delattr__(self, item): # del self.item 无限递归了 # 应使用self.__dict__.pop(item) print('删除操作__delattr__') def __setattr__(self, key, value): print('执行__setattr__') # self.key = value 这样设置会进入递归,因为__init__实际就调用了这个函数 self.__dict__[key]=value f1 = Foo(10) print(f1.y) # 10 print(getattr(f1,'y')) # 10 f1.sss # 执行__getattr__ # __getattr__调用一个对象不存在的属性时会触发运行 del f1.y del f1.x # 删除操作__delattr__ # 删除操作__delattr__ # 删除时会触发__delattr__ f1 = Foo(10) print(f1.__dict__) # 设置属性时会触发__setattr__ # 执行__setattr__ # {'y': 10} f1.z =2 print(f1.__dict__) # {'y': 10, 'z': 2}
__setattr__较常用,可定制设置属性的过程。通过这些方法可以控制类属性的删除、修改和查询,但这样用比较低端。高端用法:二次加工标准类型(包装),见后文
- __getattr__:obj.属性 不存在时触发
- __setattr__:obj.属性=属性的值 时触发
- __delattr__:del obj.属性 时触发
class Foo: def __init__(self,name): self.name = name def __getattr__(self, item): print(f'你找的属性{item}不存在') f1 = Foo('tom') print(f1.name) print(f1.age) print(f1.gender) # tom # 你找的属性age不存在 # None # 你找的属性gender不存在 # None class Foo1: def __init__(self,name): self.name = name def __getattr__(self, item): print(f'你找的属性{item}不存在') def __setattr__(self, key, value): print('执行__setattr__',key,value) # 要求属性值必须是字符串 if type(value) is str: print('开始设置') self.__dict__[key] = value else: print('必须是字符串类型') # 不写默认使用系统自带的__setattr__,将属性设置到属性字典里 # 设置以后触发这里的 def __delattr__(self,item): print(f'不允许删除属性{item}') # self.__dict__.pop(item) # 控制了类属性的删除 f2 = Foo1('tom') f2.age = 18 print(f2.__dict__) # 执行__setattr__ name tom # 开始设置 # 执行__setattr__ age 18 # 必须是字符串类型 # {'name': 'tom' del f2.name print(f2.__dict__) # 不允许删除属性name # {'name': 'tom'}
为什么用反射?
例如有两个程序员,程序员Tom在写程序的时候需要用到程序员Lucy所写的类,但是Lucy请假没在,还没有文成她写的类,Tom则可以使用反射机制继续完成自己的代码,等Lucy回来后再继续完成类的定义并且去实现Tom想要的功能。
反射的好处就是可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种“后期绑定”,即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能。
class FtpClient: 'ftp客户端,但是还没有实现具体的功能' def __init__(self,addr): self.addr = addr print(f'正在连接服务器{addr}')
# from module import FtpClient f1 = FtpClient('192.168.1.1') if hasattr(f1,'get'): func_get = getattr(f1,'get') func_get() else: print('--->方法不存在') print('处理其他的逻辑')
动态导入模块
# import s2 # 以字符串方式导入模块 m = __import__('m7.t1') print(m) # 通过这种方式以字符串导入,拿到的不一定是想要导入的模块,拿到的是最顶级的模块 # 123 文件内容 # <module 's2' from 'D:\\mycode-python\\day10\\s2.py'> print(m.t1) # <module 'm7.t1' from 'D:\\mycode-python\\day10\\m7\\t1.py'> # 通过这种方式以字符串导入,拿到的就是想要导入的模块 import importlib n = importlib.import_module('m7.t1') print(n) # <module 'm7.t1' from 'D:\\mycode-python\\day10\\m7\\t1.py'>
11. 二次加工标准类型
包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)
继承的方式完成包装授权:授权是包装的一个特性,包装一个类型通常是对已存在的类型的一些机制,这种做法可以新建、修改或删除原有产品的功能,其他则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
实现授权的关键点就是覆盖__getattr__方法
import time class Open: def __init__(self,filename,mode='r',encoding='utf-8'): # self.filename = filename self.file = open(filename,mode,encoding=encoding) self.mode = mode self.encoding = encoding def __getattr__(self, item): print(item,type(item)) #self.file.read() # 通过字符串找到自己的属性 return getattr(self.file,item) def write(self,line): t = time.strftime('%Y-%m-%d %X') self.file.write(f'{t} {line}') f1 = Open('a.txt','r+') print(f1.file) #f1.read # <_io.TextIOWrapper name='a.txt' mode='w' encoding='utf-8'> # read <class 'str'> print('-->',f1.read) # --> <built-in method read of _io.TextIOWrapper object at 0x0000017310BA7B40> sys_f = open('a.txt','w+') print('==>',getattr(sys_f,'read')) # ==> <built-in method read of _io.TextIOWrapper object at 0x0000017310BA7C18> f1.write('abc\n') f1.write('cpu负载过高\n') f1.write('硬盘剩余不足\n')
12.面向对象进阶
(1)isinstance(obj,cls)和issubclass(sub,super)
isinstance(obj,cls)检查obj(对象)是否是cls(类)的实例
issubclass(sub,super)检查sub是否是super的子类
class Foo: pass f1=Foo() print(isinstance(f1,Foo)) # True class Bar(Foo): pass print(issubclass(Bar,Foo))
class Foo: pass class Bar(Foo): pass b1 = Bar() print(isinstance(b1,Bar)) print(isinstance(b1,Foo)) # True # True
(2)__getattribute__
class Foo: def __init__(self,x): self.x = x def __getattr__(self, item): print('执行的是getattr') def __getattribute__(self, item): print('执行的是getattribute') f1 = Foo(10) print(f1.x) # 对于存在的属性,执行getattribute,由于函数无返回值,打印返回None # 执行的是getattribute # None f1.xxxx # 不存在的属性访问,也触发getattribute # 所以不论能否找到,都触发 __getattribute__ # 在__getattribute__ 中抛出AttributeError异常(raise AttributeError)时,才会触发__getattr__执行
(3)__setitem__、__getitem、__delitem__
与__setattr__、__getattr__、__delattr__效果一样,只是形式不同
通过点的方式(如del f1.name )对属性进行操作,触发的是attr系列内置函数
通过中括号的方式对属性进行操作,触发的是item系列内置函数
- __getitem__:obj [ '属性' ] 时触发
- __setitem__:obj [ '属性' ]=属性的值 时触发
- __delitem__:del obj [ '属性' ] 时触发
class Foo: def __getitem__(self, item): print('getitem') return self.__dict__[item] def __setitem__(self, key, value): print('setitem') self.__dict__[key]=value def __delitem__(self, key): print('delitem') self.__dict__.pop(key) f1 = Foo() print(f1.__dict__) f1['name']='tom' f1['age']='17' print(f1.__dict__) # {} # setitem # setitem # {'name': 'tom', 1: '123'} del f1['name'] print(f1.__dict__) # delitem # {'age': '17'} f1['age'] print(f1['age']) # getitem # getitem # 17
(4)__str__和__repr__ 改变对象的字符串显示
- 类当中有__str__方法,如果不覆盖该方法,进行实例化得到对象,打印对象的时候,实际上是在触发默认的__str__方法
- str函数或者print函数--->obj.__str__()
- repr或者交互式解释器--->obj.__repr__()
- 如果__str__没有被定义,那么就会使用__repr__来代替输出
- 这两个方法的返回值必须是字符串,否则抛出异常
(5)自定制format
format()函数就是在执行__format__函数,可以自己定制
format_dic={ 'ymd':'{0.year}{0.mon}{0.day}', 'm-d-y':'{0.mon}-{0.day}-{0.year}', 'y:m:d':'{0.year}:{0.mon}:{0.day}' } class Date: def __init__(self,year,mon,day): self.year = year self.mon = mon self.day = day def __format__(self, format_spec): if not format_spec or format_spec not in format_dic: format_spec = 'ymd' fm = format_dic[format_spec] return fm.format(self) d1 = Date(2018,8,7) print(format(d1,'ymd')) print(format(d1,'y:m:d')) print(format(d1,'m-d-y')) # 201887 # 2018:8:7 # 8-7-2018
(6)__slots__
__slots__是一个类变量,变量值可以是列表、元组,或者可迭代对象,也可以是一个字符串(即所有实例只有一个数据属性)
使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
字典会占用大量内存,如果有一个属性很少的类,但有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
当定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这与元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。
使用__slots__的缺点就是不能再给实例添加新的属性,只能使用__slots__中定义的那些属性名。
定义了__slots__后的类不再支持一些普通类特性了,比如多继承。
多数情况下,应该只在那些经常被使用到的用作数据结构的类上定义__slots__比如程序中需要创建某个类的几百万个实例对象。
__slots__的一个常见误区是它可以作为一个封装工具防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但这不是它的初衷。__slots__更多的是用来作为一个内存优化的工具。
class Foo: # __slots__ = ['name','age'] # 类似于{'name':None,'age':None} __slots__ = 'name' f1 = Foo() f1.name = 'egon' print(f1.name) # egon f1.age = 18 # 报错,此时不能再为f1添加属性 # --->setattr--->f1.__dict__['age']=18 # 此时f1没有__dict__
(7)__doc__
文档注释。该属性无法被继承。
(8)__module__和__class__
__module__表示当前操作的对象在哪个模块
__class__表示当前操作的对象的类是什么
(9)_del__析构方法
析构方法:当对象在内存中被释放时,自动触发执行。
此方法一般无需定义,因为python是一门高级语言,程序员在使用时无需关心内存的分配和释放,此工作均交给python解释器来执行。所以,析构函数的调用是由解释器在进行垃圾回收时自动触发的。
(10)__call__
对象后面加括号,触发执行。
构造方法的执行是由创建对象触发的,即:对象 = 类名();而对于__call__方法的执行是由对象后加括号触发的,即:对象()或者类()()
class Foo: def __call__(self, *args, **kwargs): print('实例执行啦') f1 = Foo() f1() # 调用的是Foo下的__call__方法 Foo() # 触发的也是__call__方法 Foo()()
(11)__next__和__iter__实现迭代器协议
在类中定义__iter__方法,将对象变成可迭代对象
class Foo: def __init__(self,n): self.n = n def __iter__(self): return self def __next__(self): if self.n == 9: raise StopIteration('终止') self.n += 1 return self.n f1 = Foo(7) print(f1.__next__()) # 8 print(f1.__next__()) # 9 print(f1.__next__()) # StopIteration: 终止
class Fib: def __init__(self,_a,_b): self._a = _a self._b = _b def __iter__(self): return self def __next__(self): if self._a > 100: raise StopIteration('终止') self._a,self._b = self._b,self._a + self._b return self._a f1 = Fib(1,1) print(f1.__next__()) print(f1.__next__()) print(f1.__next__()) print(f1.__next__()) print('==================') for i in f1: print(i) # next后再next是基于当前位置往后走,不能往回返 # 1 # 2 # 3 # 5 # ================== # 8 # 13 # 21 # 34 # 55 # 89 # 144
(12)描述符__get__,__set__,__delete__
描述符在普通开发中一般用不到,在开发大型框架会用到。
a. 描述符的本质
描述符就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议。
__get__() 调用一个属性时触发
__set__() 为一个属性赋值时触发
__delete__() 采用del删除属性时触发
class Foo: def __get__(self, instance, owner): print('get方法') def __set__(self, instance, value): print('set方法') def __delete__(self, instance): print('delete方法')
b. 描述符的作用
用来代理另外一个类的属性(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
class Foo: def __get__(self, instance, owner): print('get方法') def __set__(self, instance, value): print('set方法') def __delete__(self, instance): print('delete方法') f1 =Foo() f1.name = 'tom' print(f1.name) del f1.name # 以上均未触发
class Foo: def __get__(self, instance, owner): print('get方法') def __set__(self, instance, value): print('set方法') def __delete__(self, instance): print('delete方法') class Bar: x = Foo() # 何地使用描述符? # 何时触发描述符? b1 = Bar() b1.x # get方法 b1.x = 1 # set方法 del b1.x # delete方法
c. 描述符的分类及优先级
描述符分为两种:数据描述符(至少实现了__get__()和__set__())和非数据描述符(没有实现__set__())
注意事项:描述符本身应该定义成新式类。被代理的类也应该是新式类。必须把描述符定义成这个类的属性,不能定义到构造函数中。要严格遵循优先级,优先级由高到低依次是:类属性、数据描述符、实例属性、非数据描述符、找不到的属性触发__getattr__()。
class Foo: def __get__(self, instance, owner): print('--->get方法') def __set__(self, instance, value): print('--->set方法') def __delete__(self, instance): print('--->delete方法') class Bar: x = Foo() Bar.x print(Bar.__dict__) # --->get方法 # 之前字典中:'x': <__main__.Foo object at 0x000001B61C3E1BE0> Bar.x = 1 # 未触发__set__() # 因为类属性优先级高于数据描述符 print(Bar.__dict__) # 之后字典中:'x': 1
class Foo: def __get__(self, instance, owner): print('--->get方法') def __set__(self, instance, value): print('--->set方法') def __delete__(self, instance): print('--->delete方法') class Bar: x = Foo() b1 = Bar() # 以下触发的是数据描述符的方法 # 数据描述符优先级高于实例属性 b1.x # --->get方法 b1.x = 1 # --->set方法
class Foo: def __get__(self, instance, owner): print('--->get方法') class Bar: x = Foo() b1 = Bar() b1.x # --->get方法 # 找到了非数据描述符 b1.x = 1 print(b1.__dict__) # {'x': 1} # 实例属性优先级高于非数据描述符
class Foo: def __get__(self, instance, owner): print('--->get方法') class Bar: x = Foo() def __getattr__(self, item): print('--->getattr方法') b1 = Bar() b1.xxxxxx # --->getattr方法
d. 描述符的使用
python是弱类型语言,即参数的赋值没有类型限制,通过描述符机制可以实现类型限制功能。
# 限制name、age、salary类型 class Typed: def __get__(self, instance, owner): print('get方法') print(f'instance参数{instance}') # 实例本身 print(f'owner参数{owner}')# 实例的拥有者 类 def __set__(self, instance, value): print('set方法') print(f'instance参数{instance}') # 实例本身 print(f'value参数{value}') def __delete__(self, instance): print('delete方法') print(f'instance参数{instance}') class People: name = Typed() def __init__(self,name,age,salary): self.name = name self.age = age self.salary = salary p1 = People('tom',18,35.7) print(p1) # set方法 # instance参数<__main__.People object at 0x000001BC911C1D68> # value参数tom # <__main__.People object at 0x000001BC911C1D68> p1.name # get方法 # instance参数<__main__.People object at 0x0000022FA6B31D68> # owner参数<class '__main__.People'> print(p1.__dict__) print(p1.name) # 没有name,因为name被数据描述符代理了 # {'age': 18, 'salary': 35.7} # None
# 限制name、age、salary类型 class Typed: def __init__(self,key,expected_type): self.key = key self.expected_type = expected_type def __get__(self, instance, owner): print('get方法') return instance.__dict__[self.key] def __set__(self, instance, value): print('set方法') if not isinstance(value,self.expected_type): # if type(value) is not str raise TypeError(f'{self.key}传入的类型不是{self.expected_type}') instance.__dict__[self.key] = value def __delete__(self, instance): print('delete方法') instance.__dict__.pop(self.key) class People: name = Typed('name',str) age = Typed('age',int) def __init__(self,name,age,salary): self.name = name self.age = age self.salary = salary p1 = People('tom',18,35.7) print(p1.__dict__) p1.name = 'lucy' print(p1.__dict__) # set方法 # set方法 # {'name': 'tom', 'age': 18, 'salary': 35.7} # set方法 # {'name': 'lucy', 'age': 18, 'salary': 35.7} del p1.name print(p1.__dict__) # delete方法 # {'age': 18, 'salary': 35.7} p1.age = '12' # 抛出异常 TypeError: age传入的类型不是<class 'int'>
class Typed: def __init__(self,key,expected_type): self.key = key self.expected_type = expected_type def __get__(self, instance, owner): print('get方法') return instance.__dict__[self.key] def __set__(self, instance, value): print('set方法') if not isinstance(value,self.expected_type): # if type(value) is not str raise TypeError(f'{self.key}传入的类型不是{self.expected_type}') instance.__dict__[self.key] = value def __delete__(self, instance): print('delete方法') instance.__dict__.pop(self.key) def deco(**kwargs): def wrapper(obj): for key,val in kwargs.items(): val = Typed(key,val) setattr(obj,key,val) return obj return wrapper @deco(name=str,age=int,salary=float,gender=str) # @wrapper --> People = wrapper(People) class People: def __init__(self,name,age,salary,gender): self.name = name self.age = age self.salary = salary self.gender = gender p1 = People('tom',18,35.7,17) # TypeError: gender传入的类型不是<class 'str'>
e. 描述符总结
描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod(类方法),@staticmethod(静态方法),@property(静态属性),甚至是__slots__属性
描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰去或者元类的大型框架中的一个组件。
f. 利用描述符原理完成一个自制的@property
本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象。
class Room: def __init__(self,name,width,length): self.name = name self.width = width self.length = length @property # area = property(area) # 在给类加属性,@符号就相当于在加()执行 def area(self): return self.width * self.length r1 = Room('卧室',10,10) print(r1.area)
class Lazyproperty: def __init__(self,func): self.func = func def __get__(self, instance, owner): res = self.func(instance) return res class Room: def __init__(self,name,width,length): self.name = name self.width = width self.length = length @Lazyproperty # area = Lazyproperty(area) def area(self): return self.width * self.length r1 = Room('卧室',10,10) print(r1.area)
class Lazyproperty: def __init__(self,func): self.func = func def __get__(self, instance, owner): print('get') print(instance) print(owner) if instance is None: return self res = self.func(instance) return res class Room: def __init__(self,name,width,length): self.name = name self.width = width self.length = length @Lazyproperty # area = Lazyproperty(area) def area(self): return self.width * self.length r1 = Room('卧室',10,10) print(Room.area) # 类调用时,没有实例,instance是None # get # None # <class '__main__.Room'> # <__main__.Lazyproperty object at 0x0000029A3C791CC0>
# 通过自定制@property,实现第一次调用后,以后再调用直接取上次的值,不再次计算 class Lazyproperty: def __init__(self,func): self.func = func def __get__(self, instance, owner): print('执行get方法') if instance is None: return self res = self.func(instance) setattr(instance,self.func.__name__,res) return res # 若定义了__set__,变成了数据描述符,延迟计算功能会失效 # def __set__(self, instance, value): # pass class Room: def __init__(self,name,width,length): self.name = name self.width = width self.length = length @Lazyproperty # area = Lazyproperty(area) def area(self): return self.width * self.length @property def area1(self): return self.width * self.length r1 = Room('卧室',10,10) print(r1.area) print(r1.__dict__) # 执行get方法 # 100 # {'name': '卧室', 'width': 10, 'length': 10, 'area': 100} print(r1.area) # 此时实例属性字典里已经有area了,实例属性优先级高于非数据描述符,直接取字典里的,不再执行非数据描述符的 # 100
g. 利用描述符原理完成一个自制的@classmethod
class ClassMethod: def __init__(self,func): self.func = func def __get__(self, instance, owner): def feedback(*args,**kwargs): print('这里可以加功能') return self.func(owner,*args,**kwargs) return feedback class People: name = 'Tom' @ClassMethod def say_hi(cls,msg): print(f'Hello! {cls.name}, {msg}') People.say_hi('Nice to meet you.') # 这里可以加功能 # Hello! Tom, Nice to meet you.
h. property补充
一个静态属性property本质就是实现了get,set,delete三种方法
class Foo: @property def AAA(self): print('get的时候运行') # 静态属性设置值时必须有,否则报错 @AAA.setter def AAA(self,value): print('set的时候运行') # 静态属性删除值时必须有,否则报错 @AAA.deleter def AAA(self): print('del的时候运行') f1 = Foo() f1.AAA f1.AAA = 'abc' del f1.AAA # get的时候运行 # set的时候运行 # del的时候运行
class Foo: def get_AAA(self): print('get的时候运行') def set_AAA(self,value): print('set的时候运行') def del_AAA(self): print('del的时候运行') AAA = property(get_AAA,set_AAA,del_AAA) # 参数顺序不能变 f1 = Foo() f1.AAA f1.AAA = 'abc' del f1.AAA # get的时候运行 # set的时候运行 # del的时候运行
(13)__enter__和__exit__
操作文件对象时可以这样写
with open('a.txt') as f: '代码块'
这种做法叫作上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法。
- with obj ---> 触发 obj.__enter__(),拿到返回值
- as f ---> f=返回值
- with obj as f 等同于 f = obj.__enter__()
- 执行代码块
- 没有异常的情况下,整个代码块运行完毕后触发__exit__,它的三个参数都为None
- 有异常的情况下,从异常出现的位置直接触发__exit__。如果exit的返回值为True,代表吞掉了异常;如果不为True,代表吐出了异常
- __exit__ 的运行完毕代表了整个with语句的执行完毕
class Foo: def __init__(self,name): self.name = name def __enter__(self): print('执行enter') return self def __exit__(self, exc_type, exc_val, exc_tb): print('执行exit') with Foo('a.txt') as f: # Foo实例化触发__enter__,返回self 赋值给 f print(f) print(f.name) print('---------->') print('---------->') print('---------->') print('---------->') # 执行enter # <__main__.Foo object at 0x0000020E06641CF8> # a.txt # ----------> # ----------> # ----------> # ----------> # 执行exit
class Foo: def __init__(self,name): self.name = name def __enter__(self): print('执行enter') return self def __exit__(self, exc_type, exc_val, exc_tb): print('执行exit') print(exc_type) print(exc_val) print(exc_tb) # 分别对应异常类,异常值,异常追踪信息 # <class 'NameError'> # name 'abcde' is not defined # <traceback object at 0x0000026BBD61C148> with Foo('a.txt') as f: # Foo实例化触发__enter__,返回self 赋值给 f print(f) print(f.name) print(abcde) print('123456') # 抛出异常 NameError: name 'abcde' is not defined # 执行enter # <__main__.Foo object at 0x0000026BBD601CC0> # a.txt # 执行exit # <class 'NameError'> # name 'abcde' is not defined # <traceback object at 0x0000026BBD61C148>
class Foo: def __init__(self,name): self.name = name def __enter__(self): print('执行enter') return self def __exit__(self, exc_type, exc_val, exc_tb): print('执行exit') print(exc_type) print(exc_val) print(exc_tb) # 分别对应异常类,异常值,异常追踪信息 # <class 'NameError'> # name 'abcde' is not defined # <traceback object at 0x0000026BBD61C148> return True # 返回True 把异常吞下去了,不再报错 with Foo('a.txt') as f: print(abcde) # 抛出异常直接触发__exit__,执行完后不再执行print(f)、print(f) print(f) print(f) print('123456') # 执行enter # 执行exit # <class 'NameError'> # name 'abcde' is not defined # <traceback object at 0x000001D0980FC148> # 123456
用途(好处):
- 把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
- 在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,无须再去关心这个问题
(14)元类metaclass
a. 引子
class Foo: pass f1 = Foo() # f1 是通过Foo 类实例化的对象 # type函数可以查看类型,也可以查看对象的类 print(type(f1)) # <class '__main__.Foo'> 表示f1对象由Foo 类创建 print(type(Foo)) # <class 'type'> 类的类就是type
python中一切皆对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例)。
上例中f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是哪个类产生的呢?
b. 元类的定义
元类是类的类,是类的模板
元类是用来控制如何创建类的,正如类是创建对象的模板一样。
元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是type类的一个实例)
type是python的一个内建元类(不指定类的类是谁,默认就用type生成),用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象。
c.创建类的两种方式
# 方式一 class Foo: def __init__(self): pass print(Foo) # <class '__main__.Foo'> # 方式二 def __init__(self,name,age): self.name = name self.age = age FFo = type('FFo',(object,),{'x':1,'__init__':__init__}) # 第一个参数:类名 字符串形式 # 第二个参数:继承的父类,元组的形式 # 第三个形式:属性字典 print(FFo) # <class '__main__.FFo'> print(Foo.__dict__) # {'__module__': '__main__', '__init__': <function Foo.__init__ at 0x000001A720C73A60>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None} print(FFo.__dict__) # {'x': 1, '__init__': <function __init__ at 0x000001A720A91E18>, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'FFo' objects>, '__weakref__': <attribute '__weakref__' of 'FFo' objects>, '__doc__': None} f1 = FFo('tom',27) print(f1.name) # tom
d.自定义元类
一个类没有声明自己的元类,默认它的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类
class MyType(type): def __init__(self,a,b,c): print('元类的构造函数执行') def __call__(self, *args, **kwargs): print('----->') print(self) obj = object.__new__(self) # object.__new__(Foo),得到的是Foo的一个对象,产生f1 self.__init__(obj,*args,**kwargs) # Foo.__init__(f1,*args,**kwargs) return obj class Foo(metaclass=MyType): # 指定元类MyType会触发MyType(self,'Foo',(),{}),原本默认触发type(self,'Foo',(),{}) def __init__(self,name): self.name = name # f1.name = name print(Foo) # 元类的构造函数执行 # <class '__main__.Foo'> f1 = Foo('tom') # Foo()在呼叫MyType里的__call__方法 # -----> # <class '__main__.Foo'> print(f1) # <__main__.Foo object at 0x000001BEABB51CF8> print(f1.__dict__) # {'name': 'tom'}

浙公网安备 33010602011771号