Python基础之面向对象进阶二
一、__getattribute__
我们一看见getattribute,就想起来前面学的getattr,好了,我们先回顾一下getattr的用法吧!
class foo: def __init__(self,name): self.name = name def __getattr__(self, item): #调用不存在的方法时,会触发它的执行 print("执行了---> getattr") f1 = foo("michael") #实例化对象f1 print(f1.name) #打印查看f1的name属性 f1.xxxxx #当f1.xxxxx不存在时,会触发__getattr__的执行 --------------------输出结果-------------------------- michael 执行了---> getattr
我们再来看看getattribute的用法吧!
class foo: def __init__(self,name): self.name = name def __getattribute__(self, item): #不管方法存在与否,都会触发它的执行 print("不管是否存在,我都会执行!") f1 = foo("michael") #实例化对象f1 f1.name #查看f1的name属性,存在会触发它的执行 f1.xxxxx #f1.xxxxx不存在,也会触发它的执行 ----------------输出结果------------------- 不管是否存在,我都会执行! 不管是否存在,我都会执行!
两者的用法都清楚了,下面我们就一起来看看如果两者在一起,具体是怎么用的:
class foo: def __init__(self,name): self.name = name def __getattr__(self, item): #__getattribute__抛出异常才会触发它的执行 print("执行了-----> getattr") def __getattribute__(self, item): #不管存不存在都会触发它的执行 print("不管是否存在,我都会执行!") raise AttributeError("小子,我是故意的。") #当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,\ # 除非__getattribute__在执行过程中抛出异常AttributeError,就会执行\ #__getattr__了。 f1 = foo("michael") #实例化对象f1 f1.name #name是存在的 f1.xxxxx #xxxxx是不存在的 -----------------输出结果----------------------- 不管是否存在,我都会执行! 执行了-----> getattr 不管是否存在,我都会执行! 执行了-----> getattr
二、描述符(__get__,__set__,__delete)
1、描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这
也被称为描述符协议。
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发
class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符 def __get__(self, instance, owner): pass def __set__(self, instance, value): pass def __delete__(self, instance): pass
2、描述符是干什么的:
描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)。
引子:描述符类产生的实例进行属性操作并不会触发三个方法的执行:
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='egon' f1.name del f1.name #疑问:何时,何地,会触发这三个方法的执行
我们带着疑问,来具体看看是怎么回事?
1 #描述符Str 2 class Str: 3 def __get__(self, instance, owner): 4 print('Str调用') 5 def __set__(self, instance, value): 6 print('Str设置...') 7 def __delete__(self, instance): 8 print('Str删除...') 9 10 #描述符Int 11 class Int: 12 def __get__(self, instance, owner): 13 print('Int调用') 14 def __set__(self, instance, value): 15 print('Int设置...') 16 def __delete__(self, instance): 17 print('Int删除...') 18 19 class People: 20 name=Str() 21 age=Int() 22 def __init__(self,name,age): #name被Str类代理,age被Int类代理, 23 self.name=name 24 self.age=age 25 26 p1=People('egon',18) #实例化对象,此时,这两个参数会触发Str和Int的执行 27 -----------输出结果--------------- 28 Str设置... 29 Int设置... 30 31 # 描述符Str的使用 32 p1.name 33 p1.name='michael' 34 del p1.name 35 -------------输出结果------------- 36 Str调用 37 Str设置... 38 Str删除... 39 40 #描述符Int的使用 41 p1.age 42 p1.age=18 43 del p1.age 44 -------------输出结果------------- 45 Int调用 46 Int设置... 47 Int删除... 48 49 #我们来瞅瞅到底发生了什么 50 print(p1.__dict__) 51 print(People.__dict__) 52 ----------------------输出结果-------------------------- 53 {} 54 {'__module__': '__main__', 'name': <__main__.Str object \ 55 at 0x00000000025E00B8>, 'age': <__main__.Int object \ 56 at 0x00000000025E00F0>, '__init__': <function People.__\ 57 init__ at 0x00000000025DAC80>, '__dict__': <attribute '__\ 58 dict__' of 'People' objects>, '__weakref__': <attribute '__weakref\ 59 __' of 'People' objects>, '__doc__': None} 60 61 #补充 62 print(type(p1) == People) #type(obj)其实是查看obj是由哪个类实例化来的 63 print(type(p1).__dict__ == People.__dict__) 64 ----------------------输出结果----------------------- 65 True 66 True
3、描述符分两种:
3.1、数据描述符:至少实现了__get__()和__set__()
class Foo: def __set__(self, instance, value): print('set') def __get__(self, instance, owner): print('get')
3.2、非数据描述符:没有实现__set__()
class Foo: def __get__(self, instance, owner): print('get')
4、注意事项:
4.1、描述符本身应该定义成新式类,被代理的类也应该是新式类;
4.2、必须把描述符定义成这个类的类属性,不能为定义到构造函数中;
4.3、要严格遵循该优先级,优先级由高到底分别是:
4.3.1、类属性
4.3.2、数据描述符
4.3.3、实例属性
4.3.4、非数据描述符
4.3.5、找不到的属性触发__getattr__()
1 #描述符Str 2 class Str: 3 def __get__(self, instance, owner): 4 print('Str调用') 5 def __set__(self, instance, value): 6 print('Str设置...') 7 def __delete__(self, instance): 8 print('Str删除...') 9 10 class People: 11 name=Str() 12 def __init__(self,name,age): #name被Str类代理,age被Int类代理, 13 self.name=name 14 self.age=age 15 16 #基于上面的演示,我们已经知道,在一个类中定义描述符它就是一个类属性,存在于类的属性字典中,而不是实例的属性字典 17 18 #那既然描述符被定义成了一个类属性,直接通过类名也一定可以调用吧,没错 19 People.name #恩,调用类属性name,本质就是在调用描述符Str,触发了__get__() 20 21 People.name='egon' #那赋值呢,我去,并没有触发__set__() 22 del People.name #赶紧试试del,我去,也没有触发__delete__() 23 #结论:描述符对类没有作用-------->傻逼到家的结论 24 25 ''' 26 原因:描述符在使用时被定义成另外一个类的类属性,因而类属性比二次加工的描述符伪装而来的类属性有更高的优先级 27 People.name #恩,调用类属性name,找不到就去找描述符伪装的类属性name,触发了__get__() 28 29 People.name='egon' #那赋值呢,直接赋值了一个类属性,它拥有更高的优先级,相当于覆盖了描述符,肯定不会触发描述符的__set__() 30 del People.name #同上 31 ''' 32 类属性 > 数据描述符
1 #描述符Str 2 class Str: 3 def __get__(self, instance, owner): 4 print('Str调用') 5 def __set__(self, instance, value): 6 print('Str设置...') 7 def __delete__(self, instance): 8 print('Str删除...') 9 10 class People: 11 name=Str() 12 def __init__(self,name,age): #name被Str类代理,age被Int类代理, 13 self.name=name 14 self.age=age 15 16 p1=People('egon',18) 17 18 #如果描述符是一个数据描述符(即有__get__又有__set__),那么p1.name的调用与赋值都是触发描述符的操作,于p1本身无关了,相当于覆盖了实例的属性 19 p1.name='egonnnnnn' 20 p1.name 21 print(p1.__dict__)#实例的属性字典中没有name,因为name是一个数据描述符,优先级高于实例属性,查看/赋值/删除都是跟描述符有关,与实例无关了 22 del p1.name 23 24 数据描述符>实例属性
1 class Foo: 2 def func(self): 3 print('我胡汉三又回来了') 4 f1=Foo() 5 f1.func() #调用类的方法,也可以说是调用非数据描述符 6 #函数是一个非数据描述符对象(一切皆对象么) 7 print(dir(Foo.func)) 8 print(hasattr(Foo.func,'__set__')) 9 print(hasattr(Foo.func,'__get__')) 10 print(hasattr(Foo.func,'__delete__')) 11 #有人可能会问,描述符不都是类么,函数怎么算也应该是一个对象啊,怎么就是描述符了 12 #笨蛋哥,描述符是类没问题,描述符在应用的时候不都是实例化成一个类属性么 13 #函数就是一个由非描述符类实例化得到的对象 14 #没错,字符串也一样 15 16 f1.func='这是实例属性啊' 17 print(f1.func) 18 19 del f1.func #删掉了非数据 20 f1.func() 21 22 实例属性>非数据描述符
1 class Foo: 2 def __set__(self, instance, value): 3 print('set') 4 def __get__(self, instance, owner): 5 print('get') 6 class Room: 7 name=Foo() 8 def __init__(self,name,width,length): 9 self.name=name 10 self.width=width 11 self.length=length 12 13 #name是一个数据描述符,因为name=Foo()而Foo实现了get和set方法,因而比实例属性有更高的优先级 14 #对实例的属性操作,触发的都是描述符的 15 r1=Room('厕所',1,1) 16 r1.name 17 r1.name='厨房' 18 19 class Foo: 20 def __get__(self, instance, owner): 21 print('get') 22 class Room: 23 name=Foo() 24 def __init__(self,name,width,length): 25 self.name=name 26 self.width=width 27 self.length=length 28 29 #name是一个非数据描述符,因为name=Foo()而Foo没有实现set方法,因而比实例属性有更低的优先级 30 #对实例的属性操作,触发的都是实例自己的 31 r1=Room('厕所',1,1) 32 r1.name 33 r1.name='厨房' 34 35 再次验证:实例属性>非数据描述符
1 class Foo: 2 def func(self): 3 print('我胡汉三又回来了') 4 5 def __getattr__(self, item): 6 print('找不到了当然是来找我啦',item) 7 f1=Foo() 8 9 f1.xxxxxxxxxxx 10 11 非数据描述符>找不到
5、描述符使用
众所周知,python是弱类型语言,即参数的赋值没有类型限制,下面我们通过描述符机制来实现类型限制功能。
1 class Str: 2 def __init__(self,name): 3 self.name=name 4 def __get__(self, instance, owner): 5 print('get--->',instance,owner) 6 return instance.__dict__[self.name] 7 def __set__(self, instance, value): 8 print('set--->',instance,value) 9 instance.__dict__[self.name]=value 10 def __delete__(self, instance): 11 print('delete--->',instance) 12 instance.__dict__.pop(self.name) 13 14 class People: 15 name=Str('name') 16 def __init__(self,name,age,salary): 17 self.name=name 18 self.age=age 19 self.salary=salary 20 21 p1=People('egon',18,3231.3) 22 23 #调用 24 print(p1.__dict__) 25 p1.name 26 27 #赋值 28 print(p1.__dict__) 29 p1.name='egonlin' 30 print(p1.__dict__) 31 32 #删除 33 print(p1.__dict__) 34 del p1.name 35 print(p1.__dict__) 36 37 牛刀小试
1 class Str: 2 def __init__(self,name): 3 self.name=name 4 def __get__(self, instance, owner): 5 print('get--->',instance,owner) 6 return instance.__dict__[self.name] 7 def __set__(self, instance, value): 8 print('set--->',instance,value) 9 instance.__dict__[self.name]=value 10 def __delete__(self, instance): 11 print('delete--->',instance) 12 instance.__dict__.pop(self.name) 13 14 class People: 15 name=Str('name') 16 def __init__(self,name,age,salary): 17 self.name=name 18 self.age=age 19 self.salary=salary 20 21 #疑问:如果我用类名去操作属性呢 22 People.name #报错,错误的根源在于类去操作属性时,会把None传给instance 23 24 #修订__get__方法 25 class Str: 26 def __init__(self,name): 27 self.name=name 28 def __get__(self, instance, owner): 29 print('get--->',instance,owner) 30 if instance is None: 31 return self 32 return instance.__dict__[self.name] 33 def __set__(self, instance, value): 34 print('set--->',instance,value) 35 instance.__dict__[self.name]=value 36 def __delete__(self, instance): 37 print('delete--->',instance) 38 instance.__dict__.pop(self.name) 39 40 class People: 41 name=Str('name') 42 def __init__(self,name,age,salary): 43 self.name=name 44 self.age=age 45 self.salary=salary 46 print(People.name) #完美,解决 47 48 拔刀相助
1 class Str: 2 def __init__(self,name,expected_type): 3 self.name=name 4 self.expected_type=expected_type 5 def __get__(self, instance, owner): 6 print('get--->',instance,owner) 7 if instance is None: 8 return self 9 return instance.__dict__[self.name] 10 def __set__(self, instance, value): 11 print('set--->',instance,value) 12 if not isinstance(value,self.expected_type): #如果不是期望的类型,则抛出异常 13 raise TypeError('Expected %s' %str(self.expected_type)) 14 instance.__dict__[self.name]=value 15 def __delete__(self, instance): 16 print('delete--->',instance) 17 instance.__dict__.pop(self.name) 18 19 class People: 20 name=Str('name',str) #新增类型限制str 21 def __init__(self,name,age,salary): 22 self.name=name 23 self.age=age 24 self.salary=salary 25 26 p1=People(123,18,3333.3)#传入的name因不是字符串类型而抛出异常 27 28 磨刀霍霍
1 class Typed: 2 def __init__(self,name,expected_type): 3 self.name=name 4 self.expected_type=expected_type 5 def __get__(self, instance, owner): 6 print('get--->',instance,owner) 7 if instance is None: 8 return self 9 return instance.__dict__[self.name] 10 def __set__(self, instance, value): 11 print('set--->',instance,value) 12 if not isinstance(value,self.expected_type): 13 raise TypeError('Expected %s' %str(self.expected_type)) 14 instance.__dict__[self.name]=value 15 def __delete__(self, instance): 16 print('delete--->',instance) 17 instance.__dict__.pop(self.name) 18 19 class People: 20 name=Typed('name',str) 21 age=Typed('name',int) 22 salary=Typed('name',float) 23 def __init__(self,name,age,salary): 24 self.name=name 25 self.age=age 26 self.salary=salary 27 28 p1=People(123,18,3333.3) 29 p1=People('egon','18',3333.3) 30 p1=People('egon',18,3333) 31 32 大刀阔斧
大刀阔斧之后我们已然能实现功能了,但是问题是,如果我们的类有很多属性,你仍然采用在定义一堆类属性的方式
去实现,low,这时候我需要教你一招:独孤九剑。
1 def decorate(cls): 2 print('类的装饰器开始运行啦------>') 3 return cls 4 5 @decorate #无参:People=decorate(People) 6 class People: 7 def __init__(self,name,age,salary): 8 self.name=name 9 self.age=age 10 self.salary=salary 11 12 p1=People('egon',18,3333.3) 13 14 类的装饰器:无参
1 def typeassert(**kwargs): 2 def decorate(cls): 3 print('类的装饰器开始运行啦------>',kwargs) 4 return cls 5 return decorate 6 @typeassert(name=str,age=int,salary=float) #有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People) 7 class People: 8 def __init__(self,name,age,salary): 9 self.name=name 10 self.age=age 11 self.salary=salary 12 13 p1=People('egon',18,3333.3) 14 15 类的装饰器:有参
终极大招
1 class Typed: 2 def __init__(self,name,expected_type): 3 self.name=name 4 self.expected_type=expected_type 5 def __get__(self, instance, owner): 6 print('get--->',instance,owner) 7 if instance is None: 8 return self 9 return instance.__dict__[self.name] 10 11 def __set__(self, instance, value): 12 print('set--->',instance,value) 13 if not isinstance(value,self.expected_type): 14 raise TypeError('Expected %s' %str(self.expected_type)) 15 instance.__dict__[self.name]=value 16 def __delete__(self, instance): 17 print('delete--->',instance) 18 instance.__dict__.pop(self.name) 19 20 def typeassert(**kwargs): 21 def decorate(cls): 22 print('类的装饰器开始运行啦------>',kwargs) 23 for name,expected_type in kwargs.items(): 24 setattr(cls,name,Typed(name,expected_type)) 25 return cls 26 return decorate 27 @typeassert(name=str,age=int,salary=float) #有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People) 28 class People: 29 def __init__(self,name,age,salary): 30 self.name=name 31 self.age=age 32 self.salary=salary 33 34 print(People.__dict__) 35 p1=People('egon',18,3333.3) 36 37 刀光剑影
6、描述符总结
描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__
属性。
描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件。
7、利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描
述符:类的属性字典中函数名为key,value为描述符类产生的对象)。
1 class Room: 2 def __init__(self,name,width,length): 3 self.name=name 4 self.width=width 5 self.length=length 6 7 @property 8 def area(self): 9 return self.width * self.length 10 11 r1=Room('alex',1,1) 12 print(r1.area) 13 14 @property回顾
1 class Lazyproperty: 2 def __init__(self,func): 3 self.func=func 4 def __get__(self, instance, owner): 5 print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()') 6 if instance is None: 7 return self 8 return self.func(instance) #此时你应该明白,到底是谁在为你做自动传递self的事情 9 10 class Room: 11 def __init__(self,name,width,length): 12 self.name=name 13 self.width=width 14 self.length=length 15 16 @Lazyproperty #area=Lazyproperty(area) 相当于定义了一个类属性,即描述符 17 def area(self): 18 return self.width * self.length 19 20 r1=Room('alex',1,1) 21 print(r1.area) 22 23 自己做一个@property
1 class Lazyproperty: 2 def __init__(self,func): 3 self.func=func 4 def __get__(self, instance, owner): 5 print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()') 6 if instance is None: 7 return self 8 else: 9 print('--->') 10 value=self.func(instance) 11 setattr(instance,self.func.__name__,value) #计算一次就缓存到实例的属性字典中 12 return value 13 14 class Room: 15 def __init__(self,name,width,length): 16 self.name=name 17 self.width=width 18 self.length=length 19 20 @Lazyproperty #area=Lazyproperty(area) 相当于'定义了一个类属性,即描述符' 21 def area(self): 22 return self.width * self.length 23 24 r1=Room('alex',1,1) 25 print(r1.area) #先从自己的属性字典找,没有再去类的中找,然后出发了area的__get__方法 26 print(r1.area) #先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算 27 28 实现延迟计算功能
1 #缓存不起来了 2 3 class Lazyproperty: 4 def __init__(self,func): 5 self.func=func 6 def __get__(self, instance, owner): 7 print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()') 8 if instance is None: 9 return self 10 else: 11 value=self.func(instance) 12 instance.__dict__[self.func.__name__]=value 13 return value 14 # return self.func(instance) #此时你应该明白,到底是谁在为你做自动传递self的事情 15 def __set__(self, instance, value): 16 print('hahahahahah') 17 18 class Room: 19 def __init__(self,name,width,length): 20 self.name=name 21 self.width=width 22 self.length=length 23 24 @Lazyproperty #area=Lazyproperty(area) 相当于定义了一个类属性,即描述符 25 def area(self): 26 return self.width * self.length 27 28 print(Room.__dict__) 29 r1=Room('alex',1,1) 30 print(r1.area) 31 print(r1.area) 32 print(r1.area) 33 print(r1.area) #缓存功能失效,每次都去找描述符了,为何,因为描述符实现了set方法,它由非数据描述符变成了数据描述符,数据描述符比实例属性有更高的优先级,因而所有的属性操作都去找描述符了 34 35 一个小的改动,延迟计算的美梦就破碎了
8、利用描述符原理完成一个自定制@classmethod
1 class ClassMethod: 2 def __init__(self,func): 3 self.func=func 4 5 def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身, 6 def feedback(): 7 print('在这里可以加功能啊...') 8 return self.func(owner) 9 return feedback 10 11 class People: 12 name='linhaifeng' 13 @ClassMethod # say_hi=ClassMethod(say_hi) 14 def say_hi(cls): 15 print('你好啊,帅哥 %s' %cls.name) 16 17 People.say_hi() 18 19 p1=People() 20 p1.say_hi() 21 #疑问,类方法如果有参数呢,好说,好说 22 23 class ClassMethod: 24 def __init__(self,func): 25 self.func=func 26 27 def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身, 28 def feedback(*args,**kwargs): 29 print('在这里可以加功能啊...') 30 return self.func(owner,*args,**kwargs) 31 return feedback 32 33 class People: 34 name='linhaifeng' 35 @ClassMethod # say_hi=ClassMethod(say_hi) 36 def say_hi(cls,msg): 37 print('你好啊,帅哥 %s %s' %(cls.name,msg)) 38 39 People.say_hi('你是那偷心的贼') 40 41 p1=People() 42 p1.say_hi('你是那偷心的贼') 43 44 自己做一个@classmethod
9、利用描述符原理完成一个自定制的@staticmethod
1 class StaticMethod: 2 def __init__(self,func): 3 self.func=func 4 5 def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身, 6 def feedback(*args,**kwargs): 7 print('在这里可以加功能啊...') 8 return self.func(*args,**kwargs) 9 return feedback 10 11 class People: 12 @StaticMethod# say_hi=StaticMethod(say_hi) 13 def say_hi(x,y,z): 14 print('------>',x,y,z) 15 16 People.say_hi(1,2,3) 17 18 p1=People() 19 p1.say_hi(4,5,6) 20 21 自己做一个@staticmethod
三、__setitem__,__getitem__,__delitem__
#把对象操作属性模拟成字典的格式 class foo: def __init__(self,name): self.name=name def __getitem__(self, item): #以字典的格式获取key对应的value值 print(self.__dict__[item]) def __setitem__(self, key, value): #以字典的格式添加/修改key对应的value值 self.__dict__[key]=value def __delitem__(self, key): #以字典的格式删除key的键值对 print('del obj[key]时,我执行') self.__dict__.pop(key) def __delattr__(self, item): #以self.属性名的方式删除 print('del obj.key时,我执行') self.__dict__.pop(item) f1 = foo("egon") #实例化对象f1 f1["name"] #以字典的形式,查看name的值 f1["age"] = 18 #以字典的形式,添加age属性 f1["age1"] = 19 #以字典的形式,添加age1属性 del f1.age1 #以self.属性名的方式删除 del f1["age"] #以字典的格式删除key的键值对 f1["name"] = "michael" #以字典的形式,修改name的值 print(f1.__dict__) #打印f1的名称字典 ---------------------输出结果--------------------- egon del obj.key时,我执行 del obj[key]时,我执行 {'name': 'michael'}
四、__str__,__repr__,__format__
改变对象的字符串显示__str__,__repr__
自定制格式化字符串__format__
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) ''' str函数或者print函数--->obj.__str__() repr或者交互式解释器--->obj.__repr__() 如果__str__没有被定义,那么就会使用__repr__来代替输出 注意:这俩方法的返回值必须是字符串,否则抛出异常 ''' print(format(s1,'nat')) print(format(s1,'tna')) print(format(s1,'tan')) print(format(s1,"1"))
五、__slots__
1.__slots__是什么:
是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
2.引子:
使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
3.为何使用__slots__:
字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不
是为每个实例定义一个 字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标
上。使用__slots__一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在__slots__中定义的那些属性名。
4.注意事项:
__slots__的很多特性都依赖于普通的基于字典的实现。
另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。
大多数情况下,你应该只在那些经常被使用到的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百
万个实例对象 。
关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达
到这样的目的,但是这个并不是它的初衷。 更多的是用来作为一个内存优化工具。
class Foo: __slots__ = 'x' #所有实例只有一个数据属性x f1 = Foo() f1.x = 1 # f1.y = 2 # 报错 print(f1.__slots__) # f1不再有__dict__ print(Foo.__dict__) # 打印类的名称字典 class Bar: __slots__ = ['x', 'y'] #可以是元组、列表、字符串、可迭代对象 n = Bar() n.x = 1 n.y = 2 # n.z = 3 # 报错 print(n.__slots__) # n不再有__dict__ print(Bar.__dict__) # 打印类的名称字典 --------------输出结果----------------- x {'__module__': '__main__', '__slots__': 'x', 'x': \ <member 'x' of 'Foo' objects>, '__doc__': None} ['x', 'y'] {'__module__': '__main__', '__slots__': ['x', 'y'],\ 'x': <member 'x' of 'Bar' objects>, 'y': <member 'y'\ of 'Bar' objects>, '__doc__': None}
class Foo: __slots__=['name','age'] f1=Foo() #实例化对象f1 f1.name='alex' #给name赋值 f1.age=18 #给age赋值 print(f1.__slots__) #查看类变量 f2=Foo() #实例化对象f2 f2.name='egon' #给name赋值 f2.age=19 #给age赋值 print(f2.__slots__) #查看类变量 print(Foo.__dict__) #查看类的名称字典 #f1与f2都没有属性字典__dict__了,统一归__slots__管,节省内存
六、__iter__和__next__实现迭代器协议
from collections import Iterable,Iterator class foo: def __init__(self,start): self.start = start def __iter__(self): return self def __next__(self): if self.start >5: #当self.start的值大于5的时候主动抛出异常 raise StopIteration n = self.start self.start += 1 #没取一次,自加1 return n f = foo(0) #实例化对象f # print(next(f)) # print(next(f)) # print(next(f)) #这里next多少次,就取多少次值 for i in f: #可以全部取完 print(i) --------------输出结果----------------- 0 1 2 3 4 5
自己模拟一个range的功能:
class Range: #模拟range的功能 def __init__(self,start,end): self.start = start self.end = end def __iter__(self): #使一个可迭代对象变成一个迭代器 return self def __next__(self): # next取值 if self.start == self.end: #当开始的自加1后,等于最后一个值时,抛出异常 raise StopIteration #顾头不顾尾 n = self.start self.start += 1 return n f = Range(1,5) #实例化对象f for i in f: #取出所有的值,不会无限循环下去 print(i) ----------------输出结果----------------- 1 2 3 4
七、__doc__
class Foo: '我是描述信息' pass class Bar(Foo): pass print(Foo.__doc__) #查看Foo的类描述信息 print(Bar.__doc__) #该属性无法继承给子类 --------------输出结果---------------- 我是描述信息 None
八、__module__和__class__
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
#文件名为:aaa.py class A: def __init__(self): self.name = "michael"
from aaa import A obj = A() #实例化对象obj print(obj.name) #查看obj的属性 print(obj.__module__) #输出模块 print(obj.__class__) #输出类 ------------输出结果---------------- michael aaa <class 'aaa.A'>
九、__del__
析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作
都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
class Open: def __init__(self,filepath,mode='r',encode='utf-8'): self.f=open(filepath,mode=mode,encoding=encode) def write(self): pass def __getattr__(self, item): return getattr(self.f,item) def __del__(self): #当名称空间里有名字引用为空的时候,触发它的执行 print('----->del') self.f.close() #关闭文件 f=Open('a.txt','w') del f #删除后,f就没有引用了,此时,__del__触发执行 print('====> 继续其他的逻辑') --------------输出结果------------------ ----->del ====> 继续其他的逻辑
十、__enter__和__exit__
我们知道在操作文件对象的时候可以这么写:
with open('a.txt') as f: '代码块'
上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和
__exit__方法
class Open: def __init__(self,name): self.name=name def __enter__(self): print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') return self def __exit__(self, exc_type, exc_val, exc_tb): print('with中代码块执行完毕时执行我啊') with Open('a.txt') as f: #with : res = Open().__enter__() as : f = res print('=====>执行代码块',f) #这里的f就是实例化的对象的内存地址 print(f.name) #打印self.name ---------------------输出结果---------------------------- 出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量 =====>执行代码块 <__main__.Open object at 0x00000000025F9EB8> a.txt with中代码块执行完毕时执行我啊
__exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法
执行
class Open: def __init__(self,name): self.name=name def __enter__(self): print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') return self def __exit__(self, exc_type, exc_val, exc_tb): print('with中代码块执行完毕时执行我啊') print("exc_type:",exc_type) print("exc_val:",exc_val) print("exc_tb:",exc_tb) with Open('a.txt') as f: #with : res = Open().__enter__() as : f = res print('=====>执行代码块') #这里的f就是实例化的对象的内存地址 raise NameError("名字没有被定义!") print("看我执行不?") #抛出异常后,with后面的代码将不再执行 print("我在外面,__exit__返回布尔值必须为真,我才执行") #----->这也不会执行 -------------------------输出结果-------------------------------- Traceback (most recent call last): 出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量 File "F:/Michael/day32/note2.py", line 250, in <module> =====>执行代码块 raise NameError("名字没有被定义!") with中代码块执行完毕时执行我啊 NameError: 名字没有被定义! exc_type: <class 'NameError'> exc_val: 名字没有被定义! exc_tb: <traceback object at 0x00000000028E06C8>
如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行
class Open: def __init__(self,name): self.name=name def __enter__(self): print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') return self def __exit__(self, exc_type, exc_val, exc_tb): print('with中代码块执行完毕时执行我啊') print("exc_type:",exc_type) print("exc_val:",exc_val) print("exc_tb:",exc_tb) return True with Open('a.txt') as f: #with : res = Open().__enter__() as : f = res print('=====>执行代码块') #这里的f就是实例化的对象的内存地址 raise NameError("名字没有被定义!") print("看我执行不?") #抛出异常后,with后面的代码将不再执行 print("我在外面,__exit__返回布尔值必须为真,我才执行") --------------------------输出结果------------------------------- 出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量 =====>执行代码块 with中代码块执行完毕时执行我啊 exc_type: <class 'NameError'> exc_val: 名字没有被定义! exc_tb: <traceback object at 0x00000000026D06C8> 我在外面,__exit__返回布尔值必须为真,我才执行
用途或者说好处:
1、使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预;
2、在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,
你无须再去关系这个问题,这将大有用处。
十一、__call__
对象后面加括号,触发__call__的执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名(),而对于 __call__ 方法的执行是由对象后加括号触发
的,即:对象() 或者 类()()
class People: def __init__(self,name): self.name = name def __call__(self, *args, **kwargs): #实例化对象加括号,触发它的执行 print('call') p=People('egon') #实例化对象 print(callable(People)) #查看People是否可被调用,返回True为可调 print(callable(p)) #查看p是否可被调用,返回True为可调 p() #对象调用__call__ People("michael")() #对象调用__call__ ------------------输出结果------------------------- call call True True
十二、metaclass
1、引子
class foo: pass f1 = foo() #实例化对象f1
python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创
建一个对象(这里的对象指的是类而非类的实例)
上例可以看出f1是由foo这个类产生的对象,而foo本身也是对象,那它又是由哪个类产生的呢?
#type函数可以查看类型,也可以用来查看对象的类,二者是一样的 print(type(f1)) # 输出:<class '__main__.Foo'> 表示,obj 对象由foo类创建 print(type(foo)) # 输出:<class 'type'> 表示,foo类由type类创建的
2、什么是元类?
元类是类的类,是类的模板。
元类是用来控制如何创建类的,正如类是创建对象的模板一样
元类的实例为类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是 type 类的一个实例)
type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象。
typer----->类------>对象
3、创建类的两种方式:
方式一:
class Foo: def func(self): print('from func')
方式二:type成为元类,是所有类的类,利用type模拟class关键字的创建类的过程
def func(self): #类的函数属性 print('from func') x=1 #类的数据属性 class_name='Foo' #类的名字 bases=(object,) #类的继承,可以是多继承 class_dict = { "x":1, "func":func } #类的名称字典 Foo=type(class_name,bases,class_dict) #注意顺序一定不能错 print(Foo) -----------输出结果---------------- <class '__main__.Foo'>
4、 一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元
类(顺便我们也可以瞅一瞅元类如何控制类的创建,工作流程是什么)
1 class Mytype(type): 2 def __init__(self,class_name,bases=None,dict=None): 3 print("Mytype init--->") 4 print(class_name,type(class_name)) 5 print(bases) 6 print(dict) 7 8 def __call__(self, *args, **kwargs): 9 print('Mytype call---->',self,args,kwargs) 10 obj=self.__new__(self) 11 self.__init__(obj,*args,**kwargs) 12 return obj 13 14 class Foo(object,metaclass=Mytype):#in python3 15 #__metaclass__ = MyType #in python2 16 x=1111111111 17 def __init__(self,name): 18 self.name=name 19 20 def __new__(cls, *args, **kwargs): 21 return super().__new__(cls) 22 # return object.__new__(cls) #同上 23 24 f1=Foo('name') 25 print(f1.__dict__) 26 27 自定制元类
1 class Mytype(type): 2 def __init__(self,what,bases=None,dict=None): 3 print('mytype init') 4 5 def __call__(self, *args, **kwargs): 6 obj=self.__new__(self) 7 self.__init__(obj,*args,**kwargs) 8 return obj 9 10 class Foo(object,metaclass=Mytype): 11 x=1111111111 12 13 def __init__(self,name): 14 self.name=name 15 16 def __new__(cls, *args, **kwargs): 17 return super().__new__(cls) 18 19 f1=Foo('egon') 20 21 print(f1.__dict__) 22 23 自定制元类纯净版
1 class Mytype(type): 2 def __init__(self,what,bases=None,dict=None): 3 print(what,bases,dict) 4 5 def __call__(self, *args, **kwargs): 6 print('--->') 7 obj=object.__new__(self) 8 self.__init__(obj,*args,**kwargs) 9 return obj 10 class Room(metaclass=Mytype): 11 def __init__(self,name): 12 self.name=name 13 14 r1=Room('alex') 15 print(r1.__dict__) 16 17 自定制元类精简版
#元类总结 class Mymeta(type): def __init__(self,name,bases,dic): print('===>Mymeta.__init__') def __new__(cls, *args, **kwargs): print('===>Mymeta.__new__') return type.__new__(cls,*args,**kwargs) def __call__(self, *args, **kwargs): print('aaa') obj=self.__new__(self) self.__init__(self,*args,**kwargs) return obj class Foo(object,metaclass=Mymeta): def __init__(self,name): self.name=name def __new__(cls, *args, **kwargs): return object.__new__(cls) ---------------------分隔线------------------------ ''' 需要记住一点:名字加括号的本质(即,任何name()的形式),都是先找到name的爹,然后执行:爹.__call__ 而爹.__call__一般做两件事: 1.调用name.__new__方法并返回一个对象 2.进而调用name.__init__方法对儿子name进行初始化 ''' ---------------------分隔线------------------------ ''' class 定义Foo,并指定元类为Mymeta,这就相当于要用Mymeta创建一个新的对象Foo,于是相当于执行 Foo=Mymeta('foo',(...),{...}) 因此我们可以看到,只定义class就会有如下执行效果 ===>Mymeta.__new__ ===>Mymeta.__init__ 实际上class Foo(metaclass=Mymeta)是触发了Foo=Mymeta('Foo',(...),{...})操作, 遇到了名字加括号的形式,即Mymeta(...),于是就去找Mymeta的爹type,然后执行type.__call__(...)方法 于是触发Mymeta.__new__方法得到一个具体的对象,然后触发Mymeta.__init__方法对对象进行初始化 ''' ---------------------分隔线------------------------ ''' obj=Foo('egon') 的原理同上 ''' ---------------------分隔线------------------------ ''' 总结:元类的难点在于执行顺序很绕,其实我们只需要记住两点就可以了 1.谁后面跟括号,就从谁的爹中找__call__方法执行 type->Mymeta->Foo->obj Mymeta()触发type.__call__ Foo()触发Mymeta.__call__ obj()触发Foo.__call__ 2.__call__内按先后顺序依次调用儿子的__new__和__init__方法 '''