六 面向对象高级属性
一 isinstance(obj,cls)和issubclass(sub,super)
二 反射
三 __setattr__,__delattr__,__getattr__
四 二次加工标准类型(包装)
五 __getattribute__
六 描述符(__get__,__set__,__delete__)
七 再看property
八 __setitem__,__getitem,__delitem__
九 __str__,__repr__,__format__
十 __slots__
十一 __next__和__iter__实现迭代器协议
十二 __doc__
十三 __module__和__class__
十四 __del__
十五 __enter__和__exit__
十六 __call__
十七 metaclass
一、知识储备
二、引子(类也是对象)
三、什么是元类
四、创建元类的两种方式
五、自定义元类控制类的行为
六、应用实例
一 isinstance(obj,cls)和issubclass(sub,super)
isinstance(obj,cls)检查是否obj是否是类 cls 的对象
1 class Foo(object): 2 pass 3 4 obj = Foo() 5 6 isinstance(obj, Foo)
issubclass(sub, super)检查sub类是否是 super 类的派生类
1 class Foo(object): 2 pass 3 4 class Bar(Foo): 5 pass 6 7 issubclass(Bar, Foo)
二 反射
1 什么是反射
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。
2 python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
四个可以实现自省的函数
下列方法适用于类和对象(一切皆对象,类本身也是一个对象)
判断object中有没有一个name字符串对应的方法或属性
1 def getattr(object, name, default=None): # known special case of getattr 2 """ 3 getattr(object, name[, default]) -> value 4 5 Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y. 6 When a default argument is given, it is returned when the attribute doesn't 7 exist; without it, an exception is raised in that case. 8 """ 9 pass
1 def setattr(x, y, v): # real signature unknown; restored from __doc__ 2 """ 3 Sets the named attribute on the given object to the specified value. 4 5 setattr(x, 'y', v) is equivalent to ``x.y = v'' 6 """ 7 pass
1 def delattr(x, y): # real signature unknown; restored from __doc__ 2 """ 3 Deletes the named attribute from the given object. 4 5 delattr(x, 'y') is equivalent to ``del x.y'' 6 """ 7 pass
1 class BlackMedium: 2 feature='Ugly' 3 def __init__(self,name,addr): 4 self.name=name 5 self.addr=addr 6 7 def sell_house(self): 8 print('%s 黑中介卖房子啦,傻逼才买呢,但是谁能证明自己不傻逼' %self.name) 9 def rent_house(self): 10 print('%s 黑中介租房子啦,傻逼才租呢' %self.name) 11 12 b1=BlackMedium('万成置地','回龙观天露园') 13 14 #检测是否含有某属性 15 print(hasattr(b1,'name')) 16 print(hasattr(b1,'sell_house')) 17 18 #获取属性 19 n=getattr(b1,'name') 20 print(n) 21 func=getattr(b1,'rent_house') 22 func() 23 24 # getattr(b1,'aaaaaaaa') #报错 25 print(getattr(b1,'aaaaaaaa','不存在啊')) 26 27 #设置属性 28 setattr(b1,'sb',True) 29 setattr(b1,'show_name',lambda self:self.name+'sb') 30 print(b1.__dict__) 31 print(b1.show_name(b1)) 32 33 #删除属性 34 delattr(b1,'addr') 35 delattr(b1,'show_name') 36 delattr(b1,'show_name111')#不存在,则报错 37 38 print(b1.__dict__)
1 class Foo(object): 2 3 staticField = "old boy" 4 5 def __init__(self): 6 self.name = 'wupeiqi' 7 8 def func(self): 9 return 'func' 10 11 @staticmethod 12 def bar(): 13 return 'bar' 14 15 print getattr(Foo, 'staticField') 16 print getattr(Foo, 'func') 17 print getattr(Foo, 'bar')
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import sys 5 6 7 def s1(): 8 print 's1' 9 10 11 def s2(): 12 print 's2' 13 14 15 this_module = sys.modules[__name__] 16 17 hasattr(this_module, 's1') 18 getattr(this_module, 's2')
导入其他模块,利用反射查找该模块是否存在某个方法
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 def test(): 5 print('from the test')
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 """ 5 程序目录: 6 module_test.py 7 index.py 8 9 当前文件: 10 index.py 11 """ 12 13 import module_test as obj 14 15 #obj.test() 16 17 print(hasattr(obj,'test')) 18 19 getattr(obj,'test')()
3 为什么用反射之反射的好处
好处一:实现可插拔机制
有俩程序员,一个lili,一个是egon,lili在写程序的时候需要用到egon所写的类,但是egon去跟女朋友度蜜月去了,还没有完成他写的类,lili想到了反射,使用了反射机制lili可以继续完成自己的代码,等egon度蜜月回来后再继续完成类的定义并且去实现lili想要的功能。
总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能
好处二:动态导入模块(基于反射当前模块成员
三 __setattr__,__delattr__,__getattr__
1 class Foo: 2 x=1 3 def __init__(self,y): 4 self.y=y 5 6 def __getattr__(self, item): 7 print('----> from getattr:你找的属性不存在') 8 9 10 def __setattr__(self, key, value): 11 print('----> from setattr') 12 # self.key=value #这就无限递归了,你好好想想 13 # self.__dict__[key]=value #应该使用它 14 15 def __delattr__(self, item): 16 print('----> from delattr') 17 # del self.item #无限递归了 18 self.__dict__.pop(item) 19 20 #__setattr__添加/修改属性会触发它的执行 21 f1=Foo(10) 22 print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值 23 f1.z=3 24 print(f1.__dict__) 25 26 #__delattr__删除属性的时候会触发 27 f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作 28 del f1.a 29 print(f1.__dict__) 30 31 #__getattr__只有在使用点调用属性且属性不存在的时候才会触发 32 f1.xxxxxx 33 34 三者的用法演示
四 二次加工标准类型(包装)
包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)
1 class List(list): #继承list所有的属性,也可以派生出自己新的,比如append和mid 2 def append(self, p_object): 3 ' 派生自己的append:加上类型检查' 4 if not isinstance(p_object,int): 5 raise TypeError('must be int') 6 super().append(p_object) 7 8 @property 9 def mid(self): 10 '新增自己的属性' 11 index=len(self)//2 12 return self[index] 13 14 l=List([1,2,3,4]) 15 print(l) 16 l.append(5) 17 print(l) 18 # l.append('1111111') #报错,必须为int类型 19 20 print(l.mid) 21 22 #其余的方法都继承list的 23 l.insert(0,-123) 24 print(l) 25 l.clear() 26 print(l)
授权:授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
实现授权的关键点就是覆盖__getattr__方法
1 import time 2 class FileHandle: 3 def __init__(self,filename,mode='r',encoding='utf-8'): 4 self.file=open(filename,mode,encoding=encoding) 5 def write(self,line): 6 t=time.strftime('%Y-%m-%d %T') 7 self.file.write('%s %s' %(t,line)) 8 9 def __getattr__(self, item): 10 return getattr(self.file,item) 11 12 f1=FileHandle('b.txt','w+') 13 f1.write('你好啊') 14 f1.seek(0) 15 print(f1.read()) 16 f1.close()
1 #_*_coding:utf-8_*_ 2 __author__ = 'Linhaifeng' 3 #我们来加上b模式支持 4 import time 5 class FileHandle: 6 def __init__(self,filename,mode='r',encoding='utf-8'): 7 if 'b' in mode: 8 self.file=open(filename,mode) 9 else: 10 self.file=open(filename,mode,encoding=encoding) 11 self.filename=filename 12 self.mode=mode 13 self.encoding=encoding 14 15 def write(self,line): 16 if 'b' in self.mode: 17 if not isinstance(line,bytes): 18 raise TypeError('must be bytes') 19 self.file.write(line) 20 21 def __getattr__(self, item): 22 return getattr(self.file,item) 23 24 def __str__(self): 25 if 'b' in self.mode: 26 res="<_io.BufferedReader name='%s'>" %self.filename 27 else: 28 res="<_io.TextIOWrapper name='%s' mode='%s' encoding='%s'>" %(self.filename,self.mode,self.encoding) 29 return res 30 f1=FileHandle('b.txt','wb') 31 # f1.write('你好啊啊啊啊啊') #自定制的write,不用在进行encode转成二进制去写了,简单,大气 32 f1.write('你好啊'.encode('utf-8')) 33 print(f1) 34 f1.close()
五 __getattribute__
1 class Foo: 2 def __init__(self,x): 3 self.x=x 4 5 def __getattr__(self, item): 6 print('执行的是我') 7 # return self.__dict__[item] 8 9 f1=Foo(10) 10 print(f1.x) 11 f1.xxxxxx #不存在的属性访问,触发__getattr__
1 class Foo: 2 def __init__(self,x): 3 self.x=x 4 5 def __getattribute__(self, item): 6 print('不管是否存在,我都会执行') 7 8 f1=Foo(10) 9 f1.x 10 f1.xxxxxx
1 #_*_coding:utf-8_*_ 2 __author__ = 'Linhaifeng' 3 4 class Foo: 5 def __init__(self,x): 6 self.x=x 7 8 def __getattr__(self, item): 9 print('执行的是我') 10 # return self.__dict__[item] 11 def __getattribute__(self, item): 12 print('不管是否存在,我都会执行') 13 raise AttributeError('哈哈') 14 15 f1=Foo(10) 16 f1.x 17 f1.xxxxxx 18 19 #当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError
六 描述符(__get__,__set__,__delete__)
1 描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发
1 class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符 2 def __get__(self, instance, owner): 3 pass 4 def __set__(self, instance, value): 5 pass 6 def __delete__(self, instance): 7 pass
2 描述符是干什么的:描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
1 class Foo: 2 def __get__(self, instance, owner): 3 print('触发get') 4 def __set__(self, instance, value): 5 print('触发set') 6 def __delete__(self, instance): 7 print('触发delete') 8 9 #包含这三个方法的新式类称为描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法 10 f1=Foo() 11 f1.name='egon' 12 f1.name 13 del f1.name 14 #疑问:何时,何地,会触发这三个方法的执行
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 #何地?:定义成另外一个类的类属性 27 28 #何时?:且看下列演示 29 30 p1=People('alex',18) 31 32 #描述符Str的使用 33 p1.name 34 p1.name='egon' 35 del p1.name 36 37 #描述符Int的使用 38 p1.age 39 p1.age=18 40 del p1.age 41 42 #我们来瞅瞅到底发生了什么 43 print(p1.__dict__) 44 print(People.__dict__) 45 46 #补充 47 print(type(p1) == People) #type(obj)其实是查看obj是由哪个类实例化来的 48 print(type(p1).__dict__ == People.__dict__)
3 描述符分两种
一 数据描述符:至少实现了__get__()和__set__()
1 class Foo: 2 def __set__(self, instance, value): 3 print('set') 4 def __get__(self, instance, owner): 5 print('get')
二 非数据描述符:没有实现__set__()
1 class Foo: 2 def __get__(self, instance, owner): 3 print('get')
4 注意事项:
一 描述符本身应该定义成新式类,被代理的类也应该是新式类
二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
三 要严格遵循该优先级,优先级由高到底分别是
1.类属性
2.数据描述符
3.实例属性
4.非数据描述符
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 #那既然描述符被定义成了一个类属性,直接通过类名也一定可以调用吧,没错 20 People.name #恩,调用类属性name,本质就是在调用描述符Str,触发了__get__() 21 22 People.name='egon' #那赋值呢,我去,并没有触发__set__() 23 del People.name #赶紧试试del,我去,也没有触发__delete__() 24 #结论:描述符对类没有作用-------->傻逼到家的结论 25 26 ''' 27 原因:描述符在使用时被定义成另外一个类的类属性,因而类属性比二次加工的描述符伪装而来的类属性有更高的优先级 28 People.name #恩,调用类属性name,找不到就去找描述符伪装的类属性name,触发了__get__() 29 30 People.name='egon' #那赋值呢,直接赋值了一个类属性,它拥有更高的优先级,相当于覆盖了描述符,肯定不会触发描述符的__set__() 31 del People.name #同上 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 17 p1=People('egon',18) 18 19 #如果描述符是一个数据描述符(即有__get__又有__set__),那么p1.name的调用与赋值都是触发描述符的操作,于p1本身无关了,相当于覆盖了实例的属性 20 p1.name='egonnnnnn' 21 p1.name 22 print(p1.__dict__)#实例的属性字典中没有name,因为name是一个数据描述符,优先级高于实例属性,查看/赋值/删除都是跟描述符有关,与实例无关了 23 del p1.name
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 17 f1.func='这是实例属性啊' 18 print(f1.func) 19 20 del f1.func #删掉了非数据 21 f1.func()
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 14 #name是一个数据描述符,因为name=Foo()而Foo实现了get和set方法,因而比实例属性有更高的优先级 15 #对实例的属性操作,触发的都是描述符的 16 r1=Room('厕所',1,1) 17 r1.name 18 r1.name='厨房' 19 20 21 22 class Foo: 23 def __get__(self, instance, owner): 24 print('get') 25 class Room: 26 name=Foo() 27 def __init__(self,name,width,length): 28 self.name=name 29 self.width=width 30 self.length=length 31 32 33 #name是一个非数据描述符,因为name=Foo()而Foo没有实现set方法,因而比实例属性有更低的优先级 34 #对实例的属性操作,触发的都是实例自己的 35 r1=Room('厕所',1,1) 36 r1.name 37 r1.name='厨房'
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
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 8 def __set__(self, instance, value): 9 print('set--->',instance,value) 10 instance.__dict__[self.name]=value 11 def __delete__(self, instance): 12 print('delete--->',instance) 13 instance.__dict__.pop(self.name) 14 15 16 class People: 17 name=Str('name') 18 def __init__(self,name,age,salary): 19 self.name=name 20 self.age=age 21 self.salary=salary 22 23 p1=People('egon',18,3231.3) 24 25 #调用 26 print(p1.__dict__) 27 p1.name 28 29 #赋值 30 print(p1.__dict__) 31 p1.name='egonlin' 32 print(p1.__dict__) 33 34 #删除 35 print(p1.__dict__) 36 del p1.name 37 print(p1.__dict__)
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 8 def __set__(self, instance, value): 9 print('set--->',instance,value) 10 instance.__dict__[self.name]=value 11 def __delete__(self, instance): 12 print('delete--->',instance) 13 instance.__dict__.pop(self.name) 14 15 16 class People: 17 name=Str('name') 18 def __init__(self,name,age,salary): 19 self.name=name 20 self.age=age 21 self.salary=salary 22 23 #疑问:如果我用类名去操作属性呢 24 People.name #报错,错误的根源在于类去操作属性时,会把None传给instance 25 26 #修订__get__方法 27 class Str: 28 def __init__(self,name): 29 self.name=name 30 def __get__(self, instance, owner): 31 print('get--->',instance,owner) 32 if instance is None: 33 return self 34 return instance.__dict__[self.name] 35 36 def __set__(self, instance, value): 37 print('set--->',instance,value) 38 instance.__dict__[self.name]=value 39 def __delete__(self, instance): 40 print('delete--->',instance) 41 instance.__dict__.pop(self.name) 42 43 44 class People: 45 name=Str('name') 46 def __init__(self,name,age,salary): 47 self.name=name 48 self.age=age 49 self.salary=salary 50 print(People.name) #完美,解决
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 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 21 class People: 22 name=Str('name',str) #新增类型限制str 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)#传入的name因不是字符串类型而抛出异常
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 21 class People: 22 name=Typed('name',str) 23 age=Typed('name',int) 24 salary=Typed('name',float) 25 def __init__(self,name,age,salary): 26 self.name=name 27 self.age=age 28 self.salary=salary 29 30 p1=People(123,18,3333.3) 31 p1=People('egon','18',3333.3) 32 p1=People('egon',18,3333)
大刀阔斧之后我们已然能实现功能了,但是问题是,如果我们的类有很多属性,你仍然采用在定义一堆类属性的方式去实现,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)
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)
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)
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)
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)
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) #先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算
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方法,它由非数据描述符变成了数据描述符,数据描述符比实例属性有更高的优先级,因而所有的属性操作都去找描述符了
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('你是那偷心的贼')
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)
七 再看property
一个静态属性property本质就是实现了get,set,delete三种方法
1 class Foo: 2 @property 3 def AAA(self): 4 print('get的时候运行') 5 6 @AAA.setter 7 def AAA(self,value): 8 print('set的时候运行') 9 10 @AAA.deleter 11 def AAA(self): 12 print('delete的时候运行') 13 14 #只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter 15 f1=Foo() 16 f1.AAA 17 f1.AAA='aaa' 18 del f1.AAA
1 # 方法二 2 class Foo: 3 def get_AAA(self): 4 print('get的时候运行') 5 6 def set_AAA(self,value): 7 print('set的时候运行') 8 9 def delete_AAA(self): 10 print('delete的时候运行') 11 AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应 12 13 f1=Foo() 14 f1.AAA 15 f1.AAA='aaa' 16 del f1.AAA
案例:
1 class Goods: 2 3 def __init__(self): 4 # 原价 5 self.original_price = 100 6 # 折扣 7 self.discount = 0.8 8 9 @property 10 def price(self): 11 # 实际价格 = 原价 * 折扣 12 new_price = self.original_price * self.discount 13 return new_price 14 15 @price.setter 16 def price(self, value): 17 self.original_price = value 18 19 @price.deleter 20 def price(self): 21 del self.original_price 22 23 24 obj = Goods() 25 obj.price # 获取商品价格 26 obj.price = 200 # 修改商品原价 27 print(obj.price) 28 del obj.price # 删除商品原价
1 #实现类型检测功能 2 3 #第一关: 4 class People: 5 def __init__(self,name): 6 self.name=name 7 8 @property 9 def name(self): 10 return self.name 11 12 # p1=People('alex') #property自动实现了set和get方法属于数据描述符,比实例属性优先级高,所以你这面写会触发property内置的set,抛出异常 13 14 15 #第二关:修订版 16 17 class People: 18 def __init__(self,name): 19 self.name=name #实例化就触发property 20 21 @property 22 def name(self): 23 # return self.name #无限递归 24 print('get------>') 25 return self.DouNiWan 26 27 @name.setter 28 def name(self,value): 29 print('set------>') 30 self.DouNiWan=value 31 32 @name.deleter 33 def name(self): 34 print('delete------>') 35 del self.DouNiWan 36 37 p1=People('alex') #self.name实际是存放到self.DouNiWan里 38 print(p1.name) 39 print(p1.name) 40 print(p1.name) 41 print(p1.__dict__) 42 43 p1.name='egon' 44 print(p1.__dict__) 45 46 del p1.name 47 print(p1.__dict__) 48 49 50 #第三关:加上类型检查 51 class People: 52 def __init__(self,name): 53 self.name=name #实例化就触发property 54 55 @property 56 def name(self): 57 # return self.name #无限递归 58 print('get------>') 59 return self.DouNiWan 60 61 @name.setter 62 def name(self,value): 63 print('set------>') 64 if not isinstance(value,str): 65 raise TypeError('必须是字符串类型') 66 self.DouNiWan=value 67 68 @name.deleter 69 def name(self): 70 print('delete------>') 71 del self.DouNiWan 72 73 p1=People('alex') #self.name实际是存放到self.DouNiWan里 74 p1.name=1
八 __setitem__,__getitem,__delitem__
1 # 用法 2 class Foo: 3 def __init__(self,name): 4 self.name=name 5 6 def __getitem__(self, item): 7 print(self.__dict__[item]) 8 9 def __setitem__(self, key, value): 10 self.__dict__[key]=value 11 def __delitem__(self, key): 12 print('del obj[key]时,我执行') 13 self.__dict__.pop(key) 14 def __delattr__(self, item): 15 print('del obj.key时,我执行') 16 self.__dict__.pop(item) 17 18 f1=Foo('sb') 19 f1['age']=18 20 f1['age1']=19 21 del f1.age1 22 del f1['age'] 23 f1['name']='alex' 24 print(f1.__dict__)
九 __str__,__repr__,__format__
改变对象的字符串显示__str__,__repr__
自定制格式化字符串__format__
1 #_*_coding:utf-8_*_ 2 __author__ = 'Linhaifeng' 3 format_dict={ 4 'nat':'{obj.name}-{obj.addr}-{obj.type}',#学校名-学校地址-学校类型 5 'tna':'{obj.type}:{obj.name}:{obj.addr}',#学校类型:学校名:学校地址 6 'tan':'{obj.type}/{obj.addr}/{obj.name}',#学校类型/学校地址/学校名 7 } 8 class School: 9 def __init__(self,name,addr,type): 10 self.name=name 11 self.addr=addr 12 self.type=type 13 14 def __repr__(self): 15 return 'School(%s,%s)' %(self.name,self.addr) 16 def __str__(self): 17 return '(%s,%s)' %(self.name,self.addr) 18 19 def __format__(self, format_spec): 20 # if format_spec 21 if not format_spec or format_spec not in format_dict: 22 format_spec='nat' 23 fmt=format_dict[format_spec] 24 return fmt.format(obj=self) 25 26 s1=School('oldboy1','北京','私立') 27 print('from repr: ',repr(s1)) 28 print('from str: ',str(s1)) 29 print(s1) 30 31 ''' 32 str函数或者print函数--->obj.__str__() 33 repr或者交互式解释器--->obj.__repr__() 34 如果__str__没有被定义,那么就会使用__repr__来代替输出 35 注意:这俩方法的返回值必须是字符串,否则抛出异常 36 ''' 37 print(format(s1,'nat')) 38 print(format(s1,'tna')) 39 print(format(s1,'tan')) 40 print(format(s1,'asfdasdffd'))
1 date_dic={ 2 'ymd':'{0.year}:{0.month}:{0.day}', 3 'dmy':'{0.day}/{0.month}/{0.year}', 4 'mdy':'{0.month}-{0.day}-{0.year}', 5 } 6 class Date: 7 def __init__(self,year,month,day): 8 self.year=year 9 self.month=month 10 self.day=day 11 12 def __format__(self, format_spec): 13 if not format_spec or format_spec not in date_dic: 14 format_spec='ymd' 15 fmt=date_dic[format_spec] 16 return fmt.format(self) 17 18 d1=Date(2016,12,29) 19 print(format(d1)) 20 print('{:mdy}'.format(d1))
1 #_*_coding:utf-8_*_ 2 __author__ = 'Linhaifeng' 3 4 class A: 5 pass 6 7 class B(A): 8 pass 9 10 print(issubclass(B,A)) #B是A的子类,返回True 11 12 a1=A() 13 print(isinstance(a1,A)) #a1是A的实例
十 __slots__
1 ''' 2 1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性) 3 2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的) 4 3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__ 5 当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个 6 字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给 7 实例添加新的属性了,只能使用在__slots__中定义的那些属性名。 8 4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该 9 只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。 10 关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。 更多的是用来作为一个内存优化工具。 11 12 ''' 13 class Foo: 14 __slots__='x' 15 16 17 f1=Foo() 18 f1.x=1 19 f1.y=2#报错 20 print(f1.__slots__) #f1不再有__dict__ 21 22 class Bar: 23 __slots__=['x','y'] 24 25 n=Bar() 26 n.x,n.y=1,2 27 n.z=3#报错
1 class Foo: 2 __slots__=['name','age'] 3 4 f1=Foo() 5 f1.name='alex' 6 f1.age=18 7 print(f1.__slots__) 8 9 f2=Foo() 10 f2.name='egon' 11 f2.age=19 12 print(f2.__slots__) 13 14 print(Foo.__dict__) 15 #f1与f2都没有属性字典__dict__了,统一归__slots__管,节省内存
十一 __next__和__iter__实现迭代器协议
1 #_*_coding:utf-8_*_ 2 __author__ = 'Linhaifeng' 3 class Foo: 4 def __init__(self,x): 5 self.x=x 6 7 def __iter__(self): 8 return self 9 10 def __next__(self): 11 n=self.x 12 self.x+=1 13 return self.x 14 15 f=Foo(3) 16 for i in f: 17 print(i)
1 class Foo: 2 def __init__(self,start,stop): 3 self.num=start 4 self.stop=stop 5 def __iter__(self): 6 return self 7 def __next__(self): 8 if self.num >= self.stop: 9 raise StopIteration 10 n=self.num 11 self.num+=1 12 return n 13 14 f=Foo(1,5) 15 from collections import Iterable,Iterator 16 print(isinstance(f,Iterator)) 17 18 for i in Foo(1,5): 19 print(i)
1 class Range: 2 def __init__(self,n,stop,step): 3 self.n=n 4 self.stop=stop 5 self.step=step 6 7 def __next__(self): 8 if self.n >= self.stop: 9 raise StopIteration 10 x=self.n 11 self.n+=self.step 12 return x 13 14 def __iter__(self): 15 return self 16 17 for i in Range(1,7,3): # 18 print(i)
1 class Fib: 2 def __init__(self): 3 self._a=0 4 self._b=1 5 6 def __iter__(self): 7 return self 8 9 def __next__(self): 10 self._a,self._b=self._b,self._a + self._b 11 return self._a 12 13 f1=Fib() 14 15 print(f1.__next__()) 16 print(next(f1)) 17 print(next(f1)) 18 19 for i in f1: 20 if i > 100: 21 break 22 print('%s ' %i,end='')
十二 __doc__
1 # 类的描述信息 2 class Foo: 3 '我是描述信息' 4 pass 5 6 print(Foo.__doc__)
1 class Foo: 2 '我是描述信息' 3 pass 4 5 class Bar(Foo): 6 pass 7 print(Bar.__doc__) #该属性无法继承给子类
十三 __module__和__class__
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
1 class C: 2 3 def __init__(self): 4 self.name = ‘Lucy'
1 from lib.aa import C 2 3 obj = C() 4 print obj.__module__ # 输出 lib.aa,即:输出模块 5 print obj.__class__ # 输出 lib.aa.C,即:输出类
十四 __del__
析构方法,当对象在内存中被释放时,自动触发执行。
注:如果产生的对象仅仅只是python程序级别的(用户级),那么无需定义__del__,如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了__del__
1 class Foo: 2 3 def __del__(self): 4 print('执行我啦') 5 6 f1=Foo() 7 del f1 8 print('------->') 9 10 #输出结果 11 执行我啦 12 ------->
1 class Foo: 2 3 def __del__(self): 4 print('执行我啦') 5 6 f1=Foo() 7 del f1 8 print('------->') 9 10 #输出结果 11 执行我啦 12 ------->
典型的应用场景:
创建数据库类,用该类实例化出数据库链接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中
当程序结束时,python只会回收自己的内存空间,即用户态内存,而操作系统的资源则没有被回收,这就需要我们定制__del__,在对象被删除前向操作系统发起关闭数据库链接的系统调用,回收资源
这与文件处理是一个道理:
1 f=open('a.txt') #做了两件事,在用户空间拿到一个f变量,在操作系统内核空间打开一个文件 2 del f #只回收用户空间的f,操作系统的文件还处于打开状态 3 4 #所以我们应该在del f之前保证f.close()执行,即便是没有del,程序执行完毕也会自动del清理资源,于是文件操作的正确用法应该是 5 f=open('a.txt') 6 读写... 7 f.close() 8 很多情况下大家都容易忽略f.close,这就用到了with上下文管理
十五 __enter__和__exit__
在操作文件对象的时候可以这么写
1 with open('a.txt') as f: 2 '代码块'
上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法
1 class Open: 2 def __init__(self,name): 3 self.name=name 4 5 def __enter__(self): 6 print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') 7 # return self 8 def __exit__(self, exc_type, exc_val, exc_tb): 9 print('with中代码块执行完毕时执行我啊') 10 11 12 with Open('a.txt') as f: 13 print('=====>执行代码块') 14 # print(f,f.name)
__exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行
1 class Open: 2 def __init__(self,name): 3 self.name=name 4 5 def __enter__(self): 6 print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') 7 8 def __exit__(self, exc_type, exc_val, exc_tb): 9 print('with中代码块执行完毕时执行我啊') 10 print(exc_type) 11 print(exc_val) 12 print(exc_tb) 13 14 15 16 with Open('a.txt') as f: 17 print('=====>执行代码块') 18 raise AttributeError('***着火啦,救火啊***') 19 print('0'*100) #------------------------------->不会执行
如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行
1 class Open: 2 def __init__(self,name): 3 self.name=name 4 5 def __enter__(self): 6 print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量') 7 8 def __exit__(self, exc_type, exc_val, exc_tb): 9 print('with中代码块执行完毕时执行我啊') 10 print(exc_type) 11 print(exc_val) 12 print(exc_tb) 13 return True 14 15 16 17 with Open('a.txt') as f: 18 print('=====>执行代码块') 19 raise AttributeError('***着火啦,救火啊***') 20 print('0'*100) #------------------------------->会执行
1 class Open: 2 def __init__(self,filepath,mode='r',encoding='utf-8'): 3 self.filepath=filepath 4 self.mode=mode 5 self.encoding=encoding 6 7 def __enter__(self): 8 # print('enter') 9 self.f=open(self.filepath,mode=self.mode,encoding=self.encoding) 10 return self.f 11 12 def __exit__(self, exc_type, exc_val, exc_tb): 13 # print('exit') 14 self.f.close() 15 return True 16 def __getattr__(self, item): 17 return getattr(self.f,item) 18 19 with Open('a.txt','w') as f: 20 print(f) 21 f.write('aaaaaa') 22 f.wasdf #抛出异常,交给__exit__处理
用途或者说好处:
1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预
2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处
十六 __call__
1 对象后面加括号,触发执行。 2 3 注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
1 class Foo: 2 3 def __init__(self): 4 pass 5 6 def __call__(self, *args, **kwargs): 7 8 print('__call__') 9 10 11 obj = Foo() # 执行 __init__ 12 obj() # 执行 __call__
十七 metaclass
一、知识储备
1 exec:三个参数 2 3 参数一:字符串形式的命令 4 5 参数二:全局作用域(字典形式),如果不指定,默认为globals() 6 7 参数三:局部作用域(字典形式),如果不指定,默认为locals()
1 #可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中 2 g={ 3 'x':1, 4 'y':2 5 } 6 l={} 7 8 exec(''' 9 global x,z 10 x=100 11 z=200 12 13 m=300 14 ''',g,l) 15 16 print(g) #{'x': 100, 'y': 2,'z':200,......} 17 print(l) #{'m': 300}
二、引子(类也是对象)
1 class Foo: 2 pass 3 4 f1=Foo() #f1是通过Foo类实例化的对象
python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例),因而我们可以将类当作一个对象去使用,同样满足第一类对象的概念,可以:
-
把类赋值给一个变量
-
把类作为函数参数进行传递
-
把类作为函数的返回值
-
在运行时动态地创建类
上例可以看出f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是由哪个类产生的呢?
1 #type函数可以查看类型,也可以用来查看对象的类,二者是一样的 2 print(type(f1)) # 输出:<class '__main__.Foo'> 表示,obj 对象由Foo类创建 3 print(type(Foo)) # 输出:<type 'type'>
三、什么是元类
元类是类的类,是类的模板
元类是用来控制如何创建类的,正如类是创建对象的模板一样,而元类的主要目的是为了控制类的创建行为
元类的实例化的结果为我们用class定义的类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是 type 类的一个实例)
type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象
四、创建元类的两种方式
方式一:使用class关键字
1 class Chinese(object): 2 country='China' 3 def __init__(self,name,age): 4 self.name=name 5 self.age=age 6 def talk(self): 7 print('%s is talking' %self.name)
方式二:就是手动模拟class创建类的过程):将创建类的步骤拆分开,手动去创建
1 #准备工作: 2 #创建类主要分为三部分 3 类名 4 类的父类 5 类体 6 7 8 #类名 9 class_name='Chinese' 10 #类的父类 11 class_bases=(object,) 12 #类体 13 class_body=""" 14 country='China' 15 def __init__(self,name,age): 16 self.name=name 17 self.age=age 18 def talk(self): 19 print('%s is talking' %self.name) 20 """
步骤一(先处理类体->名称空间
类体定义的名字都会存放于类的名称空间中(一个局部的名称空间),我们可以事先定义一个空字典,然后用exec去执行类体的代码(exec产生名称空间的过程与真正的class过程类似,只是后者会将__开头的属性变形),生成类的局部名称空间,即填充字典
1 class_dic={} 2 exec(class_body,globals(),class_dic) 3 4 5 print(class_dic) 6 #{'country': 'China', 'talk': <function talk at 0x101a560c8>, '__init__': <function __init__ at 0x101a56668>}
步骤二:调用元类type(也可以自定义)来产生类Chinense
1 Foo=type(class_name,class_bases,class_dic) #实例化type得到对象Foo,即我们用class定义的类Foo 2 3 4 print(Foo) 5 print(type(Foo)) 6 print(isinstance(Foo,type)) 7 ''' 8 <class '__main__.Chinese'> 9 <class 'type'> 10 True 11 '''
我们看到,type 接收三个参数:
-
第 1 个参数是字符串 ‘Foo’,表示类名
-
第 2 个参数是元组 (object, ),表示所有的父类
-
第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法
补充:若Foo类有继承,即class Foo(Bar):.... 则等同于type('Foo',(Bar,),{})
五、自定义元类控制类的行为
#一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的行为,工作流程是什么)
1 #!!!如果你拷贝不注明出处的话,以后老子都不写了!!! 2 3 4 5 6 7 8 9 #知识储备: 10 #产生的新对象 = object.__new__(继承object类的子类) 11 12 13 14 15 16 17 18 19 #步骤一:如果说People=type(类名,类的父类们,类的名称空间),那么我们定义元类如下,来控制类的创建 20 class Mymeta(type): # 继承默认元类的一堆属性 21 def __init__(self, class_name, class_bases, class_dic): 22 if '__doc__' not in class_dic or not class_dic.get('__doc__').strip(): 23 raise TypeError('必须为类指定文档注释') 24 25 if not class_name.istitle(): 26 raise TypeError('类名首字母必须大写') 27 28 super(Mymeta, self).__init__(class_name, class_bases, class_dic) 29 30 31 class People(object, metaclass=Mymeta): 32 country = 'China' 33 34 def __init__(self, name, age): 35 self.name = name 36 self.age = age 37 38 def talk(self): 39 print('%s is talking' % self.name) 40 41 42 43 44 45 46 47 48 #步骤二:如果我们想控制类实例化的行为,那么需要先储备知识__call__方法的使用 49 class People(object,metaclass=type): 50 def __init__(self,name,age): 51 self.name=name 52 self.age=age 53 54 def __call__(self, *args, **kwargs): 55 print(self,args,kwargs) 56 57 58 # 调用类People,并不会出发__call__ 59 obj=People('egon',18) 60 61 # 调用对象obj(1,2,3,a=1,b=2,c=3),才会出发对象的绑定方法obj.__call__(1,2,3,a=1,b=2,c=3) 62 obj(1,2,3,a=1,b=2,c=3) #打印:<__main__.People object at 0x10076dd30> (1, 2, 3) {'a': 1, 'b': 2, 'c': 3} 63 64 #总结:如果说类People是元类type的实例,那么在元类type内肯定也有一个__call__,会在调用People('egon',18)时触发执行,然后返回一个初始化好了的对象obj 65 66 67 68 69 70 71 72 #步骤三:自定义元类,控制类的调用(即实例化)的过程 73 class Mymeta(type): #继承默认元类的一堆属性 74 def __init__(self,class_name,class_bases,class_dic): 75 if not class_name.istitle(): 76 raise TypeError('类名首字母必须大写') 77 78 super(Mymeta,self).__init__(class_name,class_bases,class_dic) 79 80 def __call__(self, *args, **kwargs): 81 #self=People 82 print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {} 83 84 #1、实例化People,产生空对象obj 85 obj=object.__new__(self) 86 87 88 #2、调用People下的函数__init__,初始化obj 89 self.__init__(obj,*args,**kwargs) 90 91 92 #3、返回初始化好了的obj 93 return obj 94 95 class People(object,metaclass=Mymeta): 96 country='China' 97 98 def __init__(self,name,age): 99 self.name=name 100 self.age=age 101 102 def talk(self): 103 print('%s is talking' %self.name) 104 105 106 107 obj=People('egon',18) 108 print(obj.__dict__) #{'name': 'egon', 'age': 18} 109 110 111 112 113 114 115 116 #步骤四: 117 class Mymeta(type): #继承默认元类的一堆属性 118 def __init__(self,class_name,class_bases,class_dic): 119 if not class_name.istitle(): 120 raise TypeError('类名首字母必须大写') 121 122 super(Mymeta,self).__init__(class_name,class_bases,class_dic) 123 124 def __call__(self, *args, **kwargs): 125 #self=People 126 print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {} 127 128 #1、调用self,即People下的函数__new__,在该函数内完成:1、产生空对象obj 2、初始化 3、返回obj 129 obj=self.__new__(self,*args,**kwargs) 130 131 #2、一定记得返回obj,因为实例化People(...)取得就是__call__的返回值 132 return obj 133 134 class People(object,metaclass=Mymeta): 135 country='China' 136 137 def __init__(self,name,age): 138 self.name=name 139 self.age=age 140 141 def talk(self): 142 print('%s is talking' %self.name) 143 144 145 def __new__(cls, *args, **kwargs): 146 obj=object.__new__(cls) 147 cls.__init__(obj,*args,**kwargs) 148 return obj 149 150 151 obj=People('egon',18) 152 print(obj.__dict__) #{'name': 'egon', 'age': 18} 153 154 155 156 157 158 159 #步骤五:基于元类实现单例模式,比如数据库对象,实例化时参数都一样,就没必要重复产生对象,浪费内存 160 class Mysql: 161 __instance=None 162 def __init__(self,host='127.0.0.1',port='3306'): 163 self.host=host 164 self.port=port 165 166 @classmethod 167 def singleton(cls,*args,**kwargs): 168 if not cls.__instance: 169 cls.__instance=cls(*args,**kwargs) 170 return cls.__instance 171 172 173 obj1=Mysql() 174 obj2=Mysql() 175 print(obj1 is obj2) #False 176 177 obj3=Mysql.singleton() 178 obj4=Mysql.singleton() 179 print(obj3 is obj4) #True 180 181 #应用:定制元类实现单例模式 182 class Mymeta(type): 183 def __init__(self,name,bases,dic): #定义类Mysql时就触发 184 self.__instance=None 185 super().__init__(name,bases,dic) 186 187 def __call__(self, *args, **kwargs): #Mysql(...)时触发 188 189 if not self.__instance: 190 self.__instance=object.__new__(self) #产生对象 191 self.__init__(self.__instance,*args,**kwargs) #初始化对象 192 #上述两步可以合成下面一步 193 # self.__instance=super().__call__(*args,**kwargs) 194 195 return self.__instance 196 class Mysql(metaclass=Mymeta): 197 def __init__(self,host='127.0.0.1',port='3306'): 198 self.host=host 199 self.port=port 200 201 202 obj1=Mysql() 203 obj2=Mysql() 204 205 print(obj1 is obj2)
六、应用实例
练习一:在元类中控制把自定义类的数据属性都变成大写
1 class Mymetaclass(type): 2 def __new__(cls,name,bases,attrs): 3 update_attrs={} 4 for k,v in attrs.items(): 5 if not callable(v) and not k.startswith('__'): 6 update_attrs[k.upper()]=v 7 else: 8 update_attrs[k]=v 9 return type.__new__(cls,name,bases,update_attrs) 10 11 class Chinese(metaclass=Mymetaclass): 12 country='China' 13 tag='Legend of the Dragon' #龙的传人 14 def walk(self): 15 print('%s is walking' %self.name) 16 17 18 print(Chinese.__dict__) 19 ''' 20 {'__module__': '__main__', 21 'COUNTRY': 'China', 22 'TAG': 'Legend of the Dragon', 23 'walk': <function Chinese.walk at 0x0000000001E7B950>, 24 '__dict__': <attribute '__dict__' of 'Chinese' objects>, 25 '__weakref__': <attribute '__weakref__' of 'Chinese' objects>, 26 '__doc__': None} 27 '''
练习二:在元类中控制自定义的类无需__init__方法
1.元类帮其完成创建对象,以及初始化操作;
2.要求实例化时传参必须为关键字形式,否则抛出异常TypeError: must use keyword argument
3.key作为用户自定义类产生对象的属性,且所有属性变成大写
1 class Mymetaclass(type): 2 # def __new__(cls,name,bases,attrs): 3 # update_attrs={} 4 # for k,v in attrs.items(): 5 # if not callable(v) and not k.startswith('__'): 6 # update_attrs[k.upper()]=v 7 # else: 8 # update_attrs[k]=v 9 # return type.__new__(cls,name,bases,update_attrs) 10 11 def __call__(self, *args, **kwargs): 12 if args: 13 raise TypeError('must use keyword argument for key function') 14 obj = object.__new__(self) #创建对象,self为类Foo 15 16 for k,v in kwargs.items(): 17 obj.__dict__[k.upper()]=v 18 return obj 19 20 class Chinese(metaclass=Mymetaclass): 21 country='China' 22 tag='Legend of the Dragon' #龙的传人 23 def walk(self): 24 print('%s is walking' %self.name) 25 26 27 p=Chinese(name='egon',age=18,sex='male') 28 print(p.__dict__)