在学习原来之前先讲一个exec的用法
exec:三个参数
参数一:字符串形式的命令
参数二:全局作用域(字典形式),如果不指定,默认为globals()
参数三:局部作用域(字典形式),如果不指定,默认为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}
python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例),因而我们可以将类当作一个对象去使用,同样满足第一类对象的概念,可以:
-
把类赋值给一个变量
-
把类作为函数参数进行传递
-
把类作为函数的返回值
-
在运行时动态地创建类
上例可以看出f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是由哪个类产生的呢?
元类是类的类,是类的模板
元类是用来控制如何创建类的,正如类是创建对象的模板一样,而元类的主要目的是为了控制类的创建行为
元类的实例化的结果为我们用class定义的类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是 type 类的一个实例)
type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象


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)


1 #准备工作: 2 3 #创建类主要分为三部分 4 类名 5 类的父类 6 类体 7 8 9 #类名 10 class_name='Chinese' 11 #类的父类 12 class_bases=(object,) 13 #类体 14 class_body=""" 15 country='China' 16 def __init__(self,name,age): 17 self.name=name 18 self.age=age 19 def talk(self): 20 print('%s is talking' %self.name) 21 """
步骤一(先处理类体->名称空间):类体定义的名字都会存放于类的名称空间中(一个局部的名称空间),我们可以事先定义一个空字典,然后用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


Foo=type(class_name,class_bases,class_dic) #实例化type得到对象Foo,即我们用class定义的类Foo print(Foo) print(type(Foo)) print(isinstance(Foo,type)) ''' <class '__main__.Chinese'> <class 'type'> True '''
我们看到,type 接收三个参数:
-
第 1 个参数是字符串 ‘Foo’,表示类名
-
第 2 个参数是元组 (object, ),表示所有的父类
-
第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法
补充:若Foo类有继承,即class Foo(Bar):.... 则等同于type('Foo',(Bar,),{})
#一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的行为,工作流程是什么)


1 #知识储备: 2 #产生的新对象 = object.__new__(继承object类的子类) 3 4 5 6 7 8 9 10 11 #步骤一:如果说People=type(类名,类的父类们,类的名称空间),那么我们定义元类如下,来控制类的创建 12 class Mymeta(type): # 继承默认元类的一堆属性 13 def __init__(self, class_name, class_bases, class_dic): 14 if '__doc__' not in class_dic or not class_dic.get('__doc__').strip(): 15 raise TypeError('必须为类指定文档注释') 16 17 if not class_name.istitle(): 18 raise TypeError('类名首字母必须大写') 19 20 super(Mymeta, self).__init__(class_name, class_bases, class_dic) 21 22 23 class People(object, metaclass=Mymeta): 24 country = 'China' 25 26 def __init__(self, name, age): 27 self.name = name 28 self.age = age 29 30 def talk(self): 31 print('%s is talking' % self.name) 32 33 34 35 36 37 38 39 40 #步骤二:如果我们想控制类实例化的行为,那么需要先储备知识__call__方法的使用 41 class People(object,metaclass=type): 42 def __init__(self,name,age): 43 self.name=name 44 self.age=age 45 46 def __call__(self, *args, **kwargs): 47 print(self,args,kwargs) 48 49 50 # 调用类People,并不会出发__call__ 51 obj=People('egon',18) 52 53 # 调用对象obj(1,2,3,a=1,b=2,c=3),才会出发对象的绑定方法obj.__call__(1,2,3,a=1,b=2,c=3) 54 obj(1,2,3,a=1,b=2,c=3) #打印:<__main__.People object at 0x10076dd30> (1, 2, 3) {'a': 1, 'b': 2, 'c': 3} 55 56 #总结:如果说类People是元类type的实例,那么在元类type内肯定也有一个__call__,会在调用People('egon',18)时触发执行,然后返回一个初始化好了的对象obj 57 58 59 60 61 62 63 64 #步骤三:自定义元类,控制类的调用(即实例化)的过程 65 class Mymeta(type): #继承默认元类的一堆属性 66 def __init__(self,class_name,class_bases,class_dic): 67 if not class_name.istitle(): 68 raise TypeError('类名首字母必须大写') 69 70 super(Mymeta,self).__init__(class_name,class_bases,class_dic) 71 72 def __call__(self, *args, **kwargs): 73 #self=People 74 print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {} 75 76 #1、实例化People,产生空对象obj 77 obj=object.__new__(self) 78 79 80 #2、调用People下的函数__init__,初始化obj 81 self.__init__(obj,*args,**kwargs) 82 83 84 #3、返回初始化好了的obj 85 return obj 86 87 class People(object,metaclass=Mymeta): 88 country='China' 89 90 def __init__(self,name,age): 91 self.name=name 92 self.age=age 93 94 def talk(self): 95 print('%s is talking' %self.name) 96 97 98 99 obj=People('egon',18) 100 print(obj.__dict__) #{'name': 'egon', 'age': 18} 101 102 103 104 105 106 107 108 #步骤四: 109 class Mymeta(type): #继承默认元类的一堆属性 110 def __init__(self,class_name,class_bases,class_dic): 111 if not class_name.istitle(): 112 raise TypeError('类名首字母必须大写') 113 114 super(Mymeta,self).__init__(class_name,class_bases,class_dic) 115 116 def __call__(self, *args, **kwargs): 117 #self=People 118 print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {} 119 120 #1、调用self,即People下的函数__new__,在该函数内完成:1、产生空对象obj 2、初始化 3、返回obj 121 obj=self.__new__(self,*args,**kwargs) 122 123 #2、一定记得返回obj,因为实例化People(...)取得就是__call__的返回值 124 return obj 125 126 class People(object,metaclass=Mymeta): 127 country='China' 128 129 def __init__(self,name,age): 130 self.name=name 131 self.age=age 132 133 def talk(self): 134 print('%s is talking' %self.name) 135 136 137 def __new__(cls, *args, **kwargs): 138 obj=object.__new__(cls) 139 cls.__init__(obj,*args,**kwargs) 140 return obj 141 142 143 obj=People('egon',18) 144 print(obj.__dict__) #{'name': 'egon', 'age': 18} 145 146 147 148 149 150 151 #步骤五:基于元类实现单例模式 152 # 单例:即单个实例,指的是同一个类实例化多次的结果指向同一个对象,用于节省内存空间 153 # 如果我们从配置文件中读取配置来进行实例化,在配置相同的情况下,就没必要重复产生对象浪费内存了 154 #settings.py文件内容如下 155 HOST='1.1.1.1' 156 PORT=3306 157 158 #方式一:定义一个类方法实现单例模式 159 import settings 160 161 class Mysql: 162 __instance=None 163 def __init__(self,host,port): 164 self.host=host 165 self.port=port 166 167 @classmethod 168 def singleton(cls): 169 if not cls.__instance: 170 cls.__instance=cls(settings.HOST,settings.PORT) 171 return cls.__instance 172 173 174 obj1=Mysql('1.1.1.2',3306) 175 obj2=Mysql('1.1.1.3',3307) 176 print(obj1 is obj2) #False 177 178 obj3=Mysql.singleton() 179 obj4=Mysql.singleton() 180 print(obj3 is obj4) #True 181 182 183 184 #方式二:定制元类实现单例模式 185 import settings 186 187 class Mymeta(type): 188 def __init__(self,name,bases,dic): #定义类Mysql时就触发 189 190 # 事先先从配置文件中取配置来造一个Mysql的实例出来 191 self.__instance = object.__new__(self) # 产生对象 192 self.__init__(self.__instance, settings.HOST, settings.PORT) # 初始化对象 193 # 上述两步可以合成下面一步 194 # self.__instance=super().__call__(*args,**kwargs) 195 196 197 super().__init__(name,bases,dic) 198 199 def __call__(self, *args, **kwargs): #Mysql(...)时触发 200 if args or kwargs: # args或kwargs内有值 201 obj=object.__new__(self) 202 self.__init__(obj,*args,**kwargs) 203 return obj 204 205 return self.__instance 206 207 208 209 210 class Mysql(metaclass=Mymeta): 211 def __init__(self,host,port): 212 self.host=host 213 self.port=port 214 215 216 217 obj1=Mysql() # 没有传值则默认从配置文件中读配置来实例化,所有的实例应该指向一个内存地址 218 obj2=Mysql() 219 obj3=Mysql() 220 221 print(obj1 is obj2 is obj3) 222 223 obj4=Mysql('1.1.1.4',3307) 224 225 226 227 #方式三:定义一个装饰器实现单例模式 228 import settings 229 230 def singleton(cls): #cls=Mysql 231 _instance=cls(settings.HOST,settings.PORT) 232 233 def wrapper(*args,**kwargs): 234 if args or kwargs: 235 obj=cls(*args,**kwargs) 236 return obj 237 return _instance 238 239 return wrapper 240 241 242 @singleton # Mysql=singleton(Mysql) 243 class Mysql: 244 def __init__(self,host,port): 245 self.host=host 246 self.port=port 247 248 obj1=Mysql() 249 obj2=Mysql() 250 obj3=Mysql() 251 print(obj1 is obj2 is obj3) #True 252 253 obj4=Mysql('1.1.1.3',3307) 254 obj5=Mysql('1.1.1.4',3308) 255 print(obj3 is obj4) #False
自定义原来的属性查找
结合python继承的实现原理+元类重新看属性的查找应该是什么样子呢???
在学习完元类后,其实我们用class自定义的类也全都是对象(包括object类本身也是元类type的 一个实例,可以用type(object)查看),我们学习过继承的实现原理,现在如果将下述继承说成是:对象Foo继承对象B,对象B继承对象A,对象A继承对象object
于是属性查找应该分成两层,一层是对象层(基于c3算法的MRO)的查找,另外一个层则是类层(即元类)的查找


1 class Mymeta(type): 2 n=444 3 def __call__(self, *args, **kwargs): 4 obj = self.__new__(self) # self=Foo 5 # obj = object.__new__(self) # self=Foo 6 self.__init__(obj, *args, **kwargs) 7 return obj 8 9 class A(object): 10 n=333 11 # pass 12 13 class B(A): 14 n=222 15 # pass 16 class Foo(B,metaclass=Mymeta): # Foo=Mymeta(...) 17 n=111 18 def __init__(self, x, y): 19 self.x = x 20 self.y = y 21 22 23 print(Foo.n) 24 #查找顺序: 25 #1、先对象层:Foo->B->A->object 26 #2、然后元类层:Mymeta->type
我们在元类的__call__中也可以用object.__new__(self)去造对象,先从object自己的名称空间找,由于它没有继承任何其他对象,所有直接找到它的类,object的类也是type,于是也找到type中的__new__,与此时的self.__new__(self)其实查找的最终目标是一样的
但我们还是推荐在__call__中使用self.__new__(self)去创造空对象,因为这种方式会检索Foo->Mymeta->type,我么可以在Foo或Mymeta中定制__new__,而object.__new__则是直接跨过了Foo和Mymeta
练习一:在元类中控制把自定义类的数据属性都变成大写


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__)
练习三:在元类中控制自定义的类产生的对象相关的属性全部为隐藏属性


1 class Mymeta(type): 2 def __init__(self,class_name,class_bases,class_dic): 3 #控制类Foo的创建 4 super(Mymeta,self).__init__(class_name,class_bases,class_dic) 5 6 def __call__(self, *args, **kwargs): 7 #控制Foo的调用过程,即Foo对象的产生过程 8 obj = self.__new__(self) 9 self.__init__(obj, *args, **kwargs) 10 obj.__dict__={'_%s__%s' %(self.__name__,k):v for k,v in obj.__dict__.items()} 11 12 return obj 13 14 class Foo(object,metaclass=Mymeta): # Foo=Mymeta(...) 15 def __init__(self, name, age,sex): 16 self.name=name 17 self.age=age 18 self.sex=sex 19 20 21 obj=Foo('egon',18,'male') 22 print(obj.__dict__)