python笔记 面向对象编程从入门到高级
目录:
3.2 内置方法__getatter__, __setatter__, __delatter__
3.6 __getitem__ 、 __setitem__ 、 __delitem__
3.7 __str__、__repr__、__format__
3.14 描述符(__get__,__set__,__delete__)
一、概念
类:抽象的 把一类具有共同属性和特征的事物集合到一起就是类
对象:具体的 类里面具体的某一个事物
面向对象设计:把一类具体事物的属性和特征集合到一起
面向对象编程:用定义类 + 实例/对象的方法去实现面向对象的设计
初识:
def school(name): def enrollment(name): print('%s 正在招生' %name['name']) school_dic = {'name':name, 'enrollment': enrollment} return school_dic s1 = school('xdf') s1['enrollment'](s1)
class School: # 用class定义,类名首字母大写 tag = 1 def __init__(self, name): # __init__内置方法,作用是在实例化时,自动生成实例的属性字典并返回,相当于school_dic self.Name = name # school_dic = {’name‘:name, ’enrollment':enrollment} # return school_dic def enrollment(self): print('%s 正在招生' % self.Name) s1 = School('xdf') # 实例化,实际就是调用类的init方法 ,生成了一个实例的属性字典 {‘name’:‘xdf} # 实例只有数据属性,所以实例的属性字典里面只包括了name s1.enrollment() # s1['enrollent'](s1) 实际是完成了这一步的工作,类会自动将实例本身作为参数传给self # 虽然实例属性字典里面没有函数属性,但是可以调用类的函数属性enrollment,这是因为作用域的原因,当实例不能从自己的字典中查找到的时候, #会向上到类的属性字典查找,如果也没有则会报错,不会再向上查找了 当然这种作用域的关系只存在用.的方式调用的时候
self是类帮忙自动传的一个参数,不需要程序员自己去传,实际就是把实例本身作为参数穿过去,也就是实例的属性字典
实例化的过程 ,先找到init方法 生成一个实例的属性字典
总结:类 和 实例都有自己的属性字典,类的属性字典包含了 数据属性和函数属性,实例的字典只有数据属性,这是因为函数属性不是给某一个实例的
如果实例自己有函数属性,这个属性就是一个私有的属性,不能被其他实例调用,
在用 ’.‘的方式 调用实例的属性时,遵循先从自己的字典中找,如果没有就向上到类的属性字典中找,如果也没有就终止,这也就是为什么实例能调用类的方法。
print(School.__dict__) 可以查看类的属性字典,其中就包含了类的数据属性和函数属性
{'__module__': '__main__','tag': 1, '__init__': <function School.__init__ at 0x0000014E1F23D598>, 'enrollment': <function School.enrollment at 0x0000014E1F23D488>, '__dict__': <attribute '__dict__' of 'School' objects>, '__weakref__': <attribute '__weakref__' of 'School' objects>, '__doc__': None}
print(s1.__dict__) 可以查看实例的属性字典,包含了实例的数据属性
{'Name': 'xdf'}
二、类的方法
1 class Root: 2 tag = 1 3 def __init__(self, name): 4 self. Name = name 5 @property 6 def test(self): 7 return self. Name 8 @classmethod 9 def tell_info(cls): 10 print(cls.tag) 11 @staticmethod 12 def test(a,b): 13 print(a,b) 14 R = Root('x') 15 R. name 16 R. test #不用加括号 结果一样 17 18 Root. tell_info()
@property 静态属性,将方法封装起来,调用时不需要关心内部逻辑,就像调用数据属性一样,不用括号就可以运行
@classmethod 是类方法,特殊作用,当类不通过任何实例就可以调用方法,cls参数不需要传,加上@classmethod就是一个类方法,通过.来调用类会自动将自己传入,不需要填参数。
@staticmethod静态方法,只是名义上归属类管理,不能使用类变量和实例变量,是类的工具包
class Head: pass class Hand: pass class Body: pass class Person: def __init__(self, name, age, head, hand, body): self. name = name self. age = age self. head = Head() self. hand = Hand() self. body = Body()
头手身体跟人不属于同一个类,但是又有关联,这个时候就可以用类。再比如学校有课程、老师,课程有老师,都不属于同一个类,但是都相互关联,这时就要用到类
#基类 class Foo: def f1(self): print('Foo.f1') #继承 class Bar(Foo): pass #派生 class Car(Foo): def f1(self): print('派生的Foo.f1') b=Bar() b.f1() #Foo.f1 a = Car() a.f1() #派生的Foo.f1
总结
1.当类之间有显著的不同,并且较小的类是较大的类所需的组件时,用组合比较好
2.当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好
继承顺序
深度优先(经典类)第一条线一直找到父类,没有再找另一条线到父类前面的一个类。
广度优先(新式类)python3都是新式类,先从第一条线找到父类前面的一个类,如果没找到再找另一条线直到父类。
继承原理
对于定义的每一个类python会计算出一个方法解析顺序(MRO)元组,这个元组就是一个简单的所有基类的线性顺序表。类.__mro__可以查看
所有父类的MRO表都必须遵守三条准责:
1.子类优于父类被检查,
2.多个父类会根据在mro中的顺序被检查,
3.如果对下一个类存在两个合法选择,选择第一个父类
通过MRO,Python将多继承的图结构转为list的线性结构,使super在继承体系中向上查找的过程变为了在mro列表中向右查找的过程,并且任何类只会被处理一次。
通过这个方法,Python解决的多继承中的两大难题:
- 1 查找顺序问题,从Leaf的mro顺序可以看出,如果Leaf通过super来访问父类成员,那么Medium1的成员会在Medium2之前先被访问到。如果Medium1和Medium2都没有找到,最后再到Base中查找。
- 2 钻石继承的多次初始化问题。在mro的list中Base类只出现了一次,事实上任何类都只会在mro中出现一次,这就确保了super向上调用的过程中任何祖先类的方法都只会被执行一次
1 通过super() 来调用父类的__init__ 构造方法:
2 通过supper() 来调用与子类同名的父类方法
3 通过super()调用超类的同名方法
子类中调用父类的方法
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('vehicle 开动了') class Subway(Vehicle): def __init__(self,name,speed,load,power,line): #Vehicle.__init__(self,name,speed,load,power) super().__init__(name,speed,load,power) self.line = line def run(self): #Vehicle.run(self) super().run() print('实例开动了') line2 = Subway('武汉地铁','100m/s',1000,'eletric',2) line2.run() print(super.__dict__)
Vehicle.__init__ super().__init__两种方法都可以用,但是不要混用
用到了super()的方法,好处在于父类名变了以后,子类内部程序不需要改变,这样更灵活
class A(object): def __init__(self): print("A init") class B(A): def __init__(self): super(B, self).__init__() print("B init") class C(A): def __init__(self): super(C, self).__init__() print("C init") class D(B, C): def __init__(self): super(D, self).__init__() print("D init")
super(D, self).__init__()的意思是说:
- 获取self所属类的mro, 也就是[D, B, C, A]
- 从mro中Leaf右边的一个父类类开始,依次寻找__init__函数。这里是从B开始寻找
- 一旦找到,就把找到的__init__函数绑定到self对象,并返回。
- 从这个执行流程可以看到,如果我们不想调用B的__init__,而想要调用C的__init__,那么super应该写成:super(B, self).__init__()
Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx :
super().__init__()
2、多态:多态指出了对象如何通过他们共同的属性和动作来操作及访问,而不需要考虑他们具体的类,多态实际是依附继承,当多个子类继承了同一父类,调用同一方法实现的过程和结果不同,继承的本质就是为了把共用方法放到一个基类让子类调用,一旦继承就有了多态的概念一是改变,二是扩展 就是类的两层含义的具体实现机制
第一层 是将内部逻辑打包起来,外部使用不知道内部逻辑,类的结构。
第二层 是定义私有变量,只能内部使用,“外部无法访问”,通过_ 和__约定,这只是一种约定,外部实际是可以访问,只是告诉调用者这是私有变量。
第三层 明确区分内部和外部,内部逻辑外部无法知晓,但是为外部调用内部封装的逻辑提供一个接口,这是真正意义上的封装。
归一化设计:
接口继承代表的是定义一个父类,把自己的方法利用装饰器定义成一个接口函数,父类的目的是规定子类必须实现的方法,只要有子类继承就必须实现父类的方法,不实现就没法实例化,会报错,接口类只是为了规范子类,所以里面的接口方法不需要实现,接口类也不需要实例化。
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合,一切皆文件的概念
import abc #接口继承要导入abc模块 class Allfile(metaclass=abc. ABCMeta): @abc.abstractmehod #下面的方法不具体实现 def read(self): pass @abc.abstractmehod def write(self): pass class Disk(Allfile): # 继承allfile 父类 def read(self): #必须有这两个方法,否则报错 pass def write(self):#必须有这两个方法,否则报错 pass
三、面向对象进阶
1、反射
主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)
实现反射的四个基本函数,适用于类和对象
hasattr(object,"name") #检测字符串是否在object 中,object.name是否存在,返回布尔值
getattr(object,"name",default ="字符串") #获取object. name的值,不存在就返回设置的default
setattr(object,"key","vaule") #设置属性值object. key=vaule
delattr(object,"name") #删除属性值del object. name
反射好处一:
可以事先定义好主要接口,接口只有在被完成后才会真正执行,这实现了即插即用
你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能
例:
module中功能没有完成,不影响主程序的编写,当功能完成后,主程序也不需要修改代码,直接就可以运行
class FtpClient: 'ftp客户端,但是还么有实现具体的功能' def __init__(self,addr): print('正在连接服务器[%s]' %addr) self.addr=addr # def get(self): # print('已连接服务器')
from module import FtpClient f1=FtpClient('192.168.1.1') if hasattr(f1,'get'): func_get=getattr(f1,'get') func_get() else: print('---->不存在此方法') print('处理其他的逻辑') #get方法注释掉,相当于没完成时运行的结果 #正在连接服务器[192.168.1.1] #---->不存在此方法 #处理其他的逻辑 #get方法完成,去掉注释的运行结果 #正在连接服务器[192.168.1.1] #已连接服务器
反射好处二:
动态导入模块(基于反射当前模块成员)
2、内置方法__getatter__, __setatter__, __delatter__
python的类内置了__getattr__ __setattr__ __delattr__ 隐藏属性,python直接写好的,也可以进行改写,
当调用属性不存在时会执行__getattr__ ,
当删除属性时会执行__delattr__,
当新增属性和修改属性时会执行__setattr__。只有实例调用时才会触发
class Foo: x=1 def __init__(self,y): self.y=y def __getattr__(self, item): print('----> from getattr:你找的属性不存在') def __setattr__(self, key, value): print('----> from setattr') # self.key=value #这就无限递归了,你好好想想 # self.__dict__[key]=value #应该使用它 def __delattr__(self, item): print('----> from delattr') # del self.item #无限递归了 self.__dict__.pop(item) #__setattr__添加/修改属性会触发它的执行 f1=Foo(10) #初始化也会添加实例的属性字典,触发__setattr__ #运行结果:----> from setattr # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值, # 除非你直接操作属性字典,否则永远无法赋值 print(f1.__dict__) #运行结果:{} f1.z=3 #添加属性触发__setattr__ #运行结果:----> from setattr print(f1.__dict__) #运行结果:{} #__delattr__删除属性的时候会触发 f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作 del f1.a #触发__delattr__ #运行结果:----> from delattr print(f1.__dict__) #运行结果:{} #__getattr__只有在使用点调用属性且属性不存在的时候才会触发 f1.xxxxxx #运行结果:----> from getattr:你找的属性不存在
3、二次加工标准类型 (包装/授权)
包装:
python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法
通过继承和派生的方式实现,继承系统内置的方法(类),然后进行改写成为自己适用的方法,比如继承内置的list类,
然后重新写一个append的方法,限制增加列表的数据类型。重写clear方法,加上一个清除的权限tag
class List(list): def __init__(self, item, tag=False): super().__init__(item) self.tag = tag #' 派生自己的append:加上类型检查'符合类型的添加进列表 def append(self, p_object): if not isinstance(p_object,int): raise TypeError('must be int') super().append(p_object) @property def mid(self): '新增自己的属性' index=len(self)//2 return self[index] def clear(self): if not self.tag: raise PermissionError super().clear() l=List([1,2,3,4]) print(l) #结果:[1, 2, 3, 4] l.append(5) print(l) #结果:[1, 2, 3, 4, 5] # l.append('1111111') #报错,必须为int类型 print(l.mid) #结果:3 #l.clear() #会报错PermissionError,因为tag = False l.tag = True l.clear() print(l) #结果:[] #其余的方法都继承标准库list的方法 l.insert(0,-123) print(l) #结果:[-123]
授权:
授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
包装的一种特性,只不过不是用继承的方法实现,而是用__getatter__来实现
class FileHandle: def __init__(self,filename,mode='r',encoding='utf-8'): if 'b' in mode: #获取文件句柄,类似于with open(filename,mode) as self.file self.file=open(filename,mode) else: self.file=open(filename,mode,encoding=encoding) self.filename=filename self.mode=mode self.encoding=encoding def write(self,line): if 'b' in self.mode: if not isinstance(line,bytes): raise TypeError('must be bytes') self.file.write(line) def __getattr__(self, item): print(item) return getattr(self.file,item) def __str__(self): if 'b' in self.mode: res="<_io.BufferedReader name='%s'>" %self.filename else: res="<_io.TextIOWrapper name='%s' mode='%s' encoding='%s'>" %(self.filename,self.mode,self.encoding) return res f1=FileHandle('b.txt','wb+') #f1.write('你好啊啊啊啊啊') #自定制的write,不是bytes类型会报错 f1.write('你好啊'.encode('utf-8')) print(f1) #结果:<_io.BufferedReader name='b.txt'> f1.seek(0) #将光标移到开头位置 print(f1.read().decode('utf-8')) #结果:你好啊 f1.close()
4、__getattribute__
__getattribute__是无论查找的属性存不存在都会执行,如果不存在就回抛出Attributeerro,这个错误会被__getattr__捕捉,然后执行__getattr__
class Foo: x = 1 def __init__(self): pass def __getattr__(self, item): print('执行的是我') #return self.__dict__[item] 无限循环 def __getattribute__(self, item): print('不管是否存在,我都会执行') if hasattr(Foo,str(item)): return getattr(Foo,str(item)) raise AttributeError('哈哈') #补充 raise 模拟抛出错误 f1=Foo() print(f1.x) #结果:不管是否存在,我都会执行 #结果:1 f1.xxx #结果:不管是否存在,我都会执行 '抛出的异常会被__getattr__接收 #结果:执行的是我 #当__getattribute__与__getattr__同时存在,只会执行__getattribute__,除非__getattribute__在执行过程中抛出异常AttributeError
5、isinstance和issubclass
isinstance(obj,cls)检查是否obj是否是类 cls 的对象
class Foo(object): pass obj = Foo() print(isinstance(obj, Foo)) #结果:True
issubclass(sub, super)检查sub类是否是 super 类的子类
class Foo(object): pass class Bar(Foo): pass print(issubclass(Bar, Foo)) #结果:True
6、__getitem__ 、 __setitem__ 、 __delitem__
__getitem__ 、 __setitem__ 、 __delitem__功能类似于__getattr__ __setattr__ __delattr__,实例用点调用就回执行getattr ,如果用中括号调用就回执行getitem
class Foo: def __init__(self,name): #self.name = name self[name] = name #改用了中括号方式赋值,所以触发__getitem__ def __getitem__(self, item): print(self.__dict__[item]) def __setitem__(self, key, value): print('f1[key]=value时我执行') self.__dict__[key]=value def __delitem__(self, key): print('del obj[key]时,我执行') self.__dict__.pop(key) def __setattr__(self, key, value): print('obj.key=value时我执行') self.__dict__[key]=[value] def __delattr__(self, item): print('del obj.key时,我执行') self.__dict__.pop(item) f1=Foo('sb') #初始化触发__getitem__ #运行结果:f1[key]=value时我执行 f1['age']=18 #运行结果:f1[key]=value时我执行 f1.age1=19 #运行结果:obj.key=value时我执行 del f1['age'] #运行结果:del obj[key]时,我执行 del f1.age1 #运行结果:del obj.key时,我执行
7、__str__、__repr__、__format__
改变对象的字符串显示__str__,__repr__
实例在终端上打印或查看的时候
__str__方法会在对象被打印时自动触发,print功能打印的就是它的返回值,类默认转化的字符串基本没有我们想要的一些东西,仅仅包含了类的名称以及实例的 ID
可以在类里实现__str__ 和 __repr__ 方法从而自定义类的字符串描述
format_dict={ 'nat':'{obj.name}-{obj.addr}-{obj.type}',#学校名-学校地址-学校类型 'tna':'{obj.type}:{obj.name}:{obj.addr}',#学校类型:学校名:学校地址 'tan':'{obj.type}/{obj.addr}/{obj.name}',#学校类型/学校地址/学校名 } class School: def __init__(self,name,addr,type): self.name=name self.addr=addr self.type=type def __repr__(self): return 'School(%s,%s)' %(self.name,self.addr) def __str__(self): return '(%s,%s)' %(self.name,self.addr) def __format__(self, format_spec): if not format_spec or format_spec not in format_dict: format_spec='nat' fmt=format_dict[format_spec] return fmt.format(obj=self) s1=School('oldboy1','北京','私立') print('from repr: ',repr(s1)) print('from str: ',str(s1)) print(s1) '''运行结果: from repr: School(oldboy1,北京) from str: (oldboy1,北京) (oldboy1,北京)''' print(format(s1,'nat')) print(format(s1,'tna')) print(s1.__format__('tan')) print(format(s1,'asfdasdffd')) '''运行结果 oldboy1-北京-私立 私立:oldboy1:北京 私立/北京/oldboy1 oldboy1-北京-私立''' ''' str函数或者print函数--->obj.__str__() print(s1)本质上就是调用s1.__str__方法 repr函数或者交互式解释器--->obj.__repr__() 交互IDE下直接输s1本质上就是调用s1__repr__方法 如果__str__没有被定义,那么就会使用__repr__来代替输出 注意:这俩方法的返回值必须是字符串,否则抛出异常 '''
8、__slots__
'''
1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个
字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给
实例添加新的属性了,只能使用在__slots__中定义的那些属性名。
4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该
只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。
关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。更多的是用来作为一个内存优化工具。
'''
class Foo: __slots__ = 'x' f1 = Foo() f1.x = 1 #f1.y = 2 # 报错 print(f1.__slots__) # f1不再有__dict__ #运行结果:x
class Bar: __slots__ = ['x', 'y'] n = Bar() n.x, n.y = 1, 2 # n.z = 3 # 报错 print(n.__slots__) #运行结果:['x', 'y']
class Foo: __slots__ = ['name', 'age'] f1 = Foo() f1.name = 'alex' f1.age = 18 print(f1.__slots__) #运行结果:['name', 'age'] f2 = Foo() f2.name = 'egon' f2.age = 19 print(f2.__slots__) #运行结果:['name', 'age'] print(Foo.__dict__) #运行结果:{'__module__': '__main__', '__slots__': ['name', 'age'], # 'age': <member 'age' of 'Foo' objects>, 'name': <member 'name' of 'Foo' objects>, '__doc__': None} # f1与f2都没有属性字典__dict__了,统一归__slots__管,节省内存
9、__next__和__iter__实现迭代器协议
实现迭代器协议就是可迭代对象,迭代器协议就是包含__next__和__iter__两个方法
第一件事是获得一个可迭代器,即调用了__iter__()函数。
第二件事是循环的过程,循环调用__next__()函数。
class Ran: def __init__(self,n,stop,step): self.n=n self.stop=stop self.step=step def __next__(self): if self.n >= self.stop: raise StopIteration x=self.n self.n+=self.step return x def __iter__(self): return self f1 = Ran(1,7,3) for i in f1: print(i) '''运行结果: 1 4 ''' print(f1.__next__()) #next(f1) ==>f1.__next__() #运行结果:1 print(next(f1)) #运行结果:4 print(next(f1)) #运行结果:报错 StopIteration #如果用__next__()或next()直接调用的话,超过判断条件就会抛出StopIteration #而for循环会捕捉StopIteration错误,然后终止循环
10、__doc__方法
class Foo: '我是描述信息' pass print(Foo.__doc__) #运行结果:我是描述信息 class Bar(Foo): pass print(Bar.__doc__) #该属性无法继承给子类 #运行结果:None
11、__module__和__class__
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
from lib.aa import C obj = C() print(obj.__module__) # 输出 lib.aa,即:对象属于这个模块 print(obj.__class__) # 输出 lib.aa.C,即:对象属于这个类
12、__del__析构方法
析构方法,当对象在内存中被释放时,自动触发执行。
注:如果产生的对象仅仅只是python程序级别的(用户级),那么无需定义__del__,如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了__del__
class Foo: def __del__(self): print('执行我啦') f1=Foo() del f1 #对象被删除,发起关闭内存资源请求,触发__del__ print('------->') #输出结果 # 执行我啦 # -------> class Foo: def __del__(self): print('执行我啦') f1=Foo() # del f1 print('------->') #输出结果 # -------> # 执行我啦 #程序运行完毕,触发__del__关闭内存资源
典型的应用场景:
创建数据库类,用该类实例化出数据库链接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中
当程序结束时,python只会回收自己的内存空间,即用户态内存,而操作系统的资源则没有被回收,这就需要我们定制__del__,在对象被删除前向操作系统发起关闭数据库链接的系统调用,回收资源
这与文件处理是一个道理:
f=open('a.txt') #做了两件事,在用户空间拿到一个f变量,在操作系统内核空间打开一个文件 del f #只回收用户空间的f,操作系统的文件还处于打开状态 #所以我们应该在del f之前保证f.close()执行,即便是没有del,程序执行完毕也会自动del清理资源,于是文件操作的正确用法应该是 f=open('a.txt') # 读写... f.close() # 很多情况下大家都容易忽略f.close,这就用到了with上下文管理
13、__call__方法
对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
类和对象 加()调用,实际就是在触发__call__方法,对象触发的是类的__call__方法,类触发的是元类的__call__方法
类加()调用看元类介绍
class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): print('__call__') obj = Foo() # 执行 __init__ obj() # 执行 __call__ #运行结果: __call__
14、描述符(__get__,__set__,__delete__)
描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)。
包含这三个方法的新式类称为描述符,由描述符这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发
#在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符 class Foo: def __get__(self, instance, owner): pass def __set__(self, instance, value): pass def __delete__(self, instance): pass
描述符分两种
一 数据描述符:至少实现了__get__()和__set__()
二 非数据描述符:没有实现__set__()
#描述符Str class Str: def __get__(self, instance, owner): print('Str调用') def __set__(self, instance, value): print('Str设置...') def __delete__(self, instance): print('Str删除...') #描述符Int class Int: def __get__(self, instance, owner): print('Int调用') def __set__(self, instance, value): print('Int设置...') def __delete__(self, instance): print('Int删除...') class People: name=Str() #描述符Str定义为类属性 age=Int() #描述符Int定义为类属性 def __init__(self,name,age): #name被Str类代理,age被Int类代理, self.name=name self.age=age p1=People('alex',18) #描述符Str的使用 p1.name p1.name='egon' del p1.name '''输出: Str调用 Str设置... Str删除... ''' #描述符Int的使用 p1.age p1.age=18 del p1.age '''输出: Int调用 Int设置... Int删除... ''' #我们来瞅瞅到底发生了什么 print(p1.__dict__) #运行结果:{} print(People.__dict__) #运行结果:{'__module__': '__main__', 'name': <__main__.Str object at 0x00000163B2C5E9B0>, # 'age': <__main__.Int object at 0x00000163B2C5E9E8>, # '__init__': <function People.__init__ at 0x00000163B2C56EA0>, # '__dict__': <attribute '__dict__' of 'People' objects>, # '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None} #补充 print(type(p1) == People) #type(obj)其实是查看obj是由哪个类实例化来的 #运行结果:True print(type(p1).__dict__ == People.__dict__) #运行结果:True
注意事项:
一 描述符本身应该定义成新式类,被代理的类也应该是新式类
二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
三 要严格遵循该优先级,优先级由高到底分别是
1.类属性
2.数据描述符
3.实例属性
4.非数据描述符
5.找不到的属性触发__getattr__()
描述符的使用:
python是弱类型语言,即参数的赋值没有类型限制,下面我们通过描述符机制来实现类型限制功能
class Typed: def __init__(self,feature,expected_type): self.feature=feature self.expected_type=expected_type #参数‘instance’是People产生的实例,参数‘owner’是‘instance’实例所属的类 def __get__(self, instance, owner): print('get--->',instance,owner) # 疑问:如果我用类名去操作属性呢 #People.name 报错,错误的根源在于类去操作属性时,会把None传给instance #加入判断解决这个问题 if instance is None: return self return instance.__dict__[self.feature] # 参数‘instance’是People产生的实例,参数‘value’是对应的值 def __set__(self, instance, value): print('set--->',instance,value) #检查输入的值是否属于对应的类,不是则抛出异常 if not isinstance(value,self.expected_type): raise TypeError('Expected %s' %str(self.expected_type)) instance.__dict__[self.feature]=value def __delete__(self, instance): print('delete--->',instance) instance.__dict__.pop(self.feature) class People: pname=Typed('name',str) #描述符代理类属性 page=Typed('age',int) #描述符代理类属性 psalary=Typed('salary',float) #描述符代理属性 def __init__(self,name,age,salary): #实例化的时会触发__set__方法,因为name已经被代理,相当于pname.__set__(pname, p1 ,'egon') self.pname=name #实例化的时会触发__set__方法,因为age已经被代理相当于page.__set__(page, p1 ,18) self.page=age self.psalary=salary # p1=People(123,18,3333.3) #__set__会进行类型判断,抛出类型错误,name不是字符串 # p1=People('egon','18',3333.3) #__set__会进行类型判断,抛出类型错误,age不是整数 p1=People('egon',18,3333.8) print(p1.age) # get---> <__main__.People object at 0x0000022E79BD1898> <class '__main__.People'> # 18 print(p1.name) # get---> <__main__.People object at 0x0000022E79BD1898> <class '__main__.People'> # egon print(p1.salary) # get---> <__main__.People object at 0x0000022E79BD1898> <class '__main__.People'> # 3333.8
如果我们的类有很多属性,你仍然采用在定义一堆类属性的方式去实现很麻烦,用装饰装饰器解决这个问题
#定义描述符 class Typed: def __init__(self,name,expected_type): self.name=name self.expected_type=expected_type def __get__(self, instance, owner): print('get--->',instance,owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->',instance,value) if not isinstance(value,self.expected_type): raise TypeError('Expected %s' %str(self.expected_type)) instance.__dict__[self.name]=value def __delete__(self, instance): print('delete--->',instance) instance.__dict__.pop(self.name) #定义装饰器 def typeassert(**kwargs): def decorate(cls): print('类的装饰器开始运行啦------>',kwargs) for name,expected_type in kwargs.items(): #此处为类属性添加描述符 ,实际完成了以前一堆的自定义工作 #People.name = Typed('name', str) #People.age = Typed('age',int) #People.salary = Typed('salary',float) setattr(cls,name,Typed(name,expected_type)) #==> object.key = value return cls return decorate @typeassert(name=str,age=int,salary=float) #有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People) # 类的装饰器开始运行啦------> {'name': <class 'str'>, 'age': <class 'int'>, 'salary': <class 'float'> class People: def __init__(self,name,age,salary): self.name=name self.age=age self.salary=salary print(People.__dict__) # {'__module__': '__main__', '__init__': <function People.__init__ at 0x000002735EAA6EA0>, # '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, # '__doc__': None, 'name': <__main__.Typed object at 0x000002735EAAEAC8>, # 'age': <__main__.Typed object at 0x000002735EAAEA58>, # 'salary': <__main__.Typed object at 0x000002735EAAEB00>} p1=People('egon',18,3333.3) print(p1.name) # get---> <__main__.People object at 0x000001B6352FEB70> <class '__main__.People'> # egon print(p1.age) # get---> <__main__.People object at 0x000001B6352FEB70> <class '__main__.People'> # 18 print(p1.salary) # get---> <__main__.People object at 0x000001B6352FEB70> <class '__main__.People'> # 3333.3
描述符总结
描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性
描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.
利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)
1.自定义property,计算一次就缓存到实例的属性字典中,实现延迟计算,但是如果描述符加上__set__方法, 延迟计算就失效了,因为它由非数据描述符变成了数据描述符,数据描述符比实例属性有更高的优先级,因而所有的属性操作都去找描述符了
class Lazyproperty: def __init__(self,func): self.func=func def __get__(self, instance, owner): print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()') if instance is None: return self else: print('--->') value=self.func(instance) setattr(instance,self.func.__name__,value) #计算一次就缓存到实例的属性字典中 return value 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('alex',1,1) print(r1.area) #先从自己的属性字典找,没有再去类的中找,然后出发了area的__get__方法 #运行结果:1 print(r1.area) #先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算 #运行结果:1
2.自定义classmethod方法
class ClassMethod: def __init__(self,func): self.func=func def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身, def feedback(*args,**kwargs): print('在这里可以加功能啊...') return self.func(owner,*args,**kwargs) print(feedback) return feedback class People: name='lll' @ClassMethod # say_hi=ClassMethod(say_hi) def say_hi(cls,msg): print('你好啊,帅哥 %s %s' %(cls.name,msg)) People.say_hi('你是那偷心的贼') # 在这里可以加功能啊... # 你好啊,帅哥 lll 你是那偷心的贼 p1=People() p1.say_hi('你是那偷心的贼') # 在这里可以加功能啊... # 你好啊,帅哥 lll 你是那偷心的贼 print(p1.say_hi) #p1.say_hi ===>得到函数feedback # 运行结果:<function ClassMethod.__get__.<locals>.feedback at 0x000001D791A5D488> '''整个代码执行顺序: 1.先加载所有的方法到内存,当到@语法糖的时候触发say_hi=ClassMethod(say_hi)进行实例化 2.执行p1.say_hi('你是那偷心的贼'),分为两部分,第一部分是p1.say_hi, 因为say_hi被ClassMethod描述符代理,所以会触发__get__方法,得到一个返回值feedback函数。 第二部分是p1.say_hi('你是那偷心的贼')就相当于执行feedback('你是那偷心的贼'),然后执行feedback函数代码块, self.func执行的就是执行say_hi()'''
再看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('delete的时候运行我啊') #只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter f1=Foo() f1.AAA #运行结果:get的时候运行我啊 f1.AAA='aaa' #运行结果: set的时候运行我啊 del f1.AAA #运行结果: delete的时候运行我啊
class Foo: def get_AAA(self): print('get的时候运行我啊') def set_AAA(self,value): print('set的时候运行我啊') def delete_AAA(self): print('delete的时候运行我啊') AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应 f1=Foo() f1.AAA #运行结果:get的时候运行我啊 f1.AAA='aaa' #运行结果:set的时候运行我啊 del f1.AAA #运行结果:delete的时候运行我啊
class People: def __init__(self,name): self.name=name @property def name(self): return self.name # p1=People('alex') #property自动实现了set和get方法属于数据描述符,比实例属性优先级高,所以你这面写会触发property内置的set,抛出异常
class People: def __init__(self,name): self.name=name #实例化就触发property @property def name(self): # return self.name #无限递归 print('get------>') return self.DouNiWan @name.setter def name(self,value): print('set------>') self.DouNiWan=value @name.deleter def name(self): print('delete------>') del self.DouNiWan p1=People('alex') #self.name实际是存放到self.DouNiWan里 print(p1.name) # get------> # alex print(p1.__dict__) # {'DouNiWan': 'alex'}
15、__enter__ __exit__方法
上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法
class Open: def __init__(self,name): self.name=name def __enter__(self): print('出现with语句,对象的__enter__被触发,有返回值则赋值给 as声明的变量') def __exit__(self, exc_type, exc_val, exc_tb): print('with中代码块执行完毕时执行我啊') print(exc_type)#错误类型 print(exc_val)#错误的具体内容 print(exc_tb)#错误追溯 return True with Open('a.txt') as f: print('=====>执行代码块') raise AttributeError('***着火啦,救火啊***') print('0'*100) #如果__exit__返回的是False此行不会执行 '''出现with语句,对象的__enter__被触发,有返回值则赋值给 as声明的变量 =====>执行代码块 with中代码块执行完毕时执行我啊 <class 'AttributeError'> ***着火啦,救火啊*** <traceback object at 0x000002661A3B7A08> 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 '''
解释:
with obj as f:
'代码块'
一. with obj ----> 触发obj.__enter__(),拿到返回值
二. as f -----> f = 返回值
三. with obj as f 等同于 f = obj.__enter__()
四. 执行代码块
1、没有异常的情况下,整个代码块运行完毕后去触发__exit__,它的三个参数都为None
2、有异常的情况下,从异常出现的位置直接触发__exit__运行
a:如果__exit__的返回值为True,代表解释器不会报错,继续执行后面的代码
b:如果__exit__的返回值不为True,代表解释器会报错,终止程序的运行
c:__exit__的运行完毕就代表整个with语句的执行完毕,程序自动清理链接
用途:
1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处
16、元类metaclass
我们用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类(元类可以简称为类的类),内置的元类为type
class Foo(): pass f = Foo() print(type(f)) print(type(Foo)) #运行结果: #<class '__main__.Foo'> #对象的类是class定义的Foo #<class 'type'> #Foo的类是内置的元类type
type是内置的元类,类是对象的模板,元类是类的模板,用来控制类的生成
知道元类type是类的模板,那么类的创建方式就有两种
1.class定义类
class Foo(): x = 1 def __init__(self,name): self.name = name def f1(self): print(self.name) f = Foo('lqs') print(type(Foo)) print(f) print(f.name) # <class 'type'> # <__main__.Foo object at 0x000002050715B780> # lqs
2.type定义类
def __init__(self,name): self.name = name def f1(self): print(self.name) #类是由type创建,相当于type类进行实例化得到Foo这个类 #type()第一个参数是类名,第二个参数是继承的类,元组形式可以继承多个类 ,新式类默认就要继承object, #第三个参数是类的属性字典,包括数据属性和函数属性 Foo = type('Foo', (object,), {'x':1, 'name':'lqs', '__init__':__init__, 'f1':f1}) f = Foo('lqs') print(type(Foo)) print(f) print(f.name) # <class 'type'> # <__main__.Foo object at 0x0000028967B2E908> # lqs
如果一个类没有声明元类,那么默认就继承元类。也可以自定义元类,控制类的生成过程
#自定义元类 class MyType(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类 def __init__(self, a, b, c): print('元类的构造函数执行') def __call__(self, *args, **kwargs): obj = object.__new__(self) #object.__new__(Foo) ,用类Foo创建并返回一个新的对象,然后赋值给obj self.__init__(obj,*args,**kwargs) #Foo.__init__(obj,*args,**kwargs) return obj class Foo(metaclass=MyType): #Foo = MyType('Foo',(object,),{'__init__':__init__}) 触发MyType的构造函数 def __init__(self, name): self.name = name #----> f1.name = 'LQS' f1 = Foo('LQS') # __call__方法时提过,对象加()就是在调用类的__call__方法,Foo的类是MyType #Foo('LQS')得到的一个返回值obj对象,赋值给f1 print(f1.name) #运行结果:LQS
总结:
产生类Foo的过程就是在调用MyType,而MyType也是type类的一个对象,那么MyType之所以可以调用,一定是在元类type中有一个__call__方法 该方法中同样需要做至少三件事: class type: def __call__(self, *args, **kwargs): #self=<class '__main__.MyType'> obj=self.__new__(self,*args,**kwargs) # 产生MyType的一个对象 self.__init__(obj,*args,**kwargs) return obj
属性查找顺序
属性查找应该分成两层,一层是对象层(基于c3算法的MRO)的查找,另外一个层则是类层(即元类层)的查找
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类 n=444 def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'> obj=self.__new__(self) self.__init__(obj,*args,**kwargs) return obj class Bar(object): n=333 class Foo(Bar): n=222 class OldboyTeacher(Foo,metaclass=Mymeta): n=111 school='oldboy' def __init__(self,name,age): self.name=name self.age=age def say(self): print('%s says welcome to the oldboy to learn Python' %self.name) print(OldboyTeacher.n) #自下而上依次注释各个类中的n=xxx,然后重新运行程序,发现n的查找顺序为OldboyTeacher->Foo->Bar->object->Mymeta->type