目录:
元类介绍
class关键字创建类的流程分析
自定义元类控制类StanfordTeacher的创建
自定义元类控制类StanfordTeacher的调用
再看属性查找
一 元类介绍
如果一切皆为对象,那么类StanfordTeacher本质也是一个对象,既然所有的对象都是调用类得到的,那么StanfordTeacher必然也是调用了一个类得到的,这个类称为元类
于是我们可以推导出===>产生StanfordTeacher的过程一定发生了:StanfordTeacher=元类(...)
type元类
类本身也是对象, 是通过实例化元类得到的对象, 元类是类的类
python默认的元类是type, 也可以自定义元类来实现控制类的创建,控制类的调用
通过type元类来创建类 MyClass = type(class_name, class_bases, class_dict)
class_name 类名
class_bases 父类, 以元祖形式传入
class_dict 类的属性, 以字典的形式传入 (类的名称空间)
#用type元类来创建一个Chinese类
def __init__(self, name, gender, age):
self.name=name
self.gender=gender
self.age=age
class_name= 'Chinese'class_bases=(object,)
class_dict= {'country': 'China', '__init__': __init__}
Chinese=type(class_name, class_bases, class_dict)
c1= Chinese('bigb', 'male', 18)print(c1.name) #bigb
print(c1.country) #China
二 class关键字创建类的流程分析
class关键字帮我们创建一个类应该细分为以下四个过程
创建类的两种方式
方式一:使用class关键字
class Chinese(object):
country='China'
def __init__(self,name,age):
self.name=name
self.age=age
def talk(self):
print('%s is talking' %self.name)
方式二:就是手动模拟class创建类的过程):将创建类的步骤拆分开,手动去创建
#准备工作:
#创建类主要分为三部分
1 类名
2 类的父类
3 类的__dict__
#类名
class_name='Chinese'
#类的父类
class_bases=(object,)
#类体
class_body="""
country='China'
def __init__(self,name,age):
self.name=name
self.age=age
def talk(self):
print('%s is talking' %self.name)
"""
步骤一(先处理类体 --> 名称空间):类定义的名字都会存放于类的名称空间中(一个局部的名称空间),我们可以实现定义一个空字典,然后用exec去执行类体的代码(exec产生名称空间的过程与真正的class过程相似,只是后者将__开头的属性变形),生成类的局部空间名称,即填充字典
class_dic={}
exec(class_body,globals(),class_dic)
print(class_dic)
#{'country': 'China', 'talk': , '__init__': }
步骤二:调用元类type(也可以自定义)来产生类Chinense
Foo=type(class_name,class_bases,class_dic) #实例化type得到对象Foo,即我们用class定义的类Foo
print(Foo)
print(type(Foo))
print(isinstance(Foo,type))
'''
True
'''
四 自定义元类控制类StanfordTeacher的创建
一个类没有声明自己的元类,默认他的元类就是type,除了使用内置元类type,我们也可以通过继承type来自定义元类,然后使用metaclass关键字参数为一个类指定元类.
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
pass
#StanfordTeacher=Mymeta('StanfordTeacher',(object),{...})
class StanfordTeacher(object,metaclass=Mymeta):
school='Stanford'
def __init__(self,name,age):
self.name=name
self.age=agedefsay(self):print('%s says welcome to the Stanford to learn Python' %self.name)
自定义元类可以控制类的产生过程,类的产生过程其实就是元类的调用过程,即StanfordTeacher=Mymeta('StanfordTeacher',(object),{...}),调用Mymeta会先产生一个空对象StanfordTeacher,然后连同调用Mymeta括号内的参数一同传给Mymeta下的__init__方法,完成初始化,于是我们可以
class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
def __init__(self,class_name,class_bases,class_dic):#print(self) #
#print(class_bases) #(,)
#print(class_dic) #{'__module__': '__main__', '__qualname__': 'StanfordTeacher', 'school': 'Stanford', '__init__': , 'say': }
super(Mymeta, self).__init__(class_name, class_bases, class_dic) #重用父类的功能
ifclass_name.islower():raise TypeError('类名%s请修改为驼峰体' %class_name)if '__doc__' not in class_dic or len(class_dic['__doc__'].strip('\n')) ==0:raise TypeError('类中必须有文档注释,并且文档注释不能为空')#StanfordTeacher=Mymeta('StanfordTeacher',(object),{...})
class StanfordTeacher(object,metaclass=Mymeta):"""类StanfordTeacher的文档注释"""school='Stanford'
def __init__(self,name,age):
self.name=name
self.age=agedefsay(self):print('%s says welcome to the Stanford to learn Python' %self.name)
五 自定义元类控制类StanfordTeacher的调用
调用一个对象,就是触发对象所在类中的__call__方法的执行
类本身也是个对象, 因此在调用类实例化对象的时候, 就会触发元类当中的__call__ 方法
classMyMeta(type):def __call__(self, *args, **kwargs):#产生一个空对象
obj = self.__new__(self) #self是类对象
#初始化空对象
self.__init__(obj, *args, **kwargs)#返回初始化好的对象
returnobjclass Chinese(object, metaclass=MyMeta): #foo = MyMeta('foo', (object, ) {...})
country = 'China'
def __init__(self, name, gender, age):
self.name=name
self.gender=gender
self.age=agedefkongfu(self):print('降龙十八掌!')#这里调用了类对象Chinese, 因此会触发Chinese的类(元类)中的__call__方法
c1 = Chinese('bigb', 'male', 18)print(c1.name)'''1. __call__中,会调用chinese内的__new__生成了一个空对象
2. __call__中,会调用Chinese内的__init__初始化这个空对象
3. __call__返回了这个对象,并赋值给了c1'''
现在 我们可以在此基础上通过修改 __call__ 的逻辑从而控制类的调用过程
#通过元类让Chinese类实例化出来的对象的属性变为私有属性
classMymeta(type):def __init__(self,class_name,class_bases,class_dic):#控制类Foo的创建
super(Mymeta,self).__init__(class_name,class_bases,class_dic)def __call__(self, *args, **kwargs):#控制Foo的调用过程,即Foo对象的产生过程
obj = self.__new__(self)print(self)print(self.__new__ is object.__new__)
self.__init__(obj, *args, **kwargs)#将对象的属性变成私有属性(对象._类__属性名)
obj.__dict__={'_%s__%s' %(self.__name__,k):v for k,v in obj.__dict__.items()}
#obj.__dict__ = {f'_{self.__name__}__{k}': v for k, v in obj.__dict__.items()}returnobjclass Foo(object,metaclass=Mymeta): #Foo=Mymeta(...)
def __init__(self, name, age,sex):
self.name=name
self.age=age
self.sex=sex
obj= Foo('SS',11,'MALE')print(obj.__dict__)
五 再看属性查找
属性查找应该分成两层,一层是对象层(基于c3算法的MRO)的查找,另外一个层则是类层(即元类层)的查找
#查找顺序:
#1、先对象层:StanfordTeacher->Foo->Bar->object
#2、然后元类层:Mymeta->type
Mymeta下的__call__里的self.__new__在StanfordTeacher、Foo、Bar里都没有找到__new__的情况下,会去找object里的__new__,而object下默认就有一个__new__,所以即便是之前的类均未实现__new__,也一定会在object中找到一个,根本不会、也根本没必要再去找元类Mymeta->type中查找__new__
但我们还是推荐在__call__中使用self.__new__(self)去创造空对象,因为这种方式会检索三个类StanfordTeacher->Foo->Bar,而object.__new__则是直接跨过了他们三个
六 作业 记得要看要写
1、在元类中控制把自定义类的数据属性都变成大写
classMymetaclass(type):def __new__(cls,name,bases,attrs):
update_attrs={}for k,v inattrs.items():if not callable(v) and not k.startswith('__'):
update_attrs[k.upper()]=velse:
update_attrs[k]=vreturn type.__new__(cls,name,bases,update_attrs)class Chinese(metaclass=Mymetaclass):
country='China'tag='Legend of the Dragon' #龙的传人
defwalk(self):print('%s is walking' %self.name)print(Chinese.__dict__)'''{'__module__': '__main__',
'COUNTRY': 'China',
'TAG': 'Legend of the Dragon',
'walk': ,
'__dict__': ,
'__weakref__': ,
'__doc__': None}'''
2、在元类中控制自定义的类无需__init__方法
1.元类帮其完成创建对象,以及初始化操作;
2.要求实例化时传参必须为关键字形式,否则抛出异常TypeError: must use keyword argument
3.key作为用户自定义类产生对象的属性,且所有属性变成大写
classMymetaclass(type):#def __new__(cls,name,bases,attrs):
#update_attrs={}
#for k,v in attrs.items():
#if not callable(v) and not k.startswith('__'):
#update_attrs[k.upper()]=v
#else:
#update_attrs[k]=v
#return type.__new__(cls,name,bases,update_attrs)
def __call__(self, *args, **kwargs):ifargs:raise TypeError('must use keyword argument for key function')
obj= object.__new__(self) #创建对象,self为类Foo
for k,v inkwargs.items():
obj.__dict__[k.upper()]=vreturnobjclass Chinese(metaclass=Mymetaclass):
country='China'tag='Legend of the Dragon' #龙的传人
defwalk(self):print('%s is walking' %self.name)
p=Chinese(name='lili',age=18,sex='male')print(p.__dict__)
3、在元类中控制自定义的类产生的对象相关的属性全部为隐藏属性
classMymeta(type):def __init__(self,class_name,class_bases,class_dic):#控制类Foo的创建
super(Mymeta,self).__init__(class_name,class_bases,class_dic)def __call__(self, *args, **kwargs):#控制Foo的调用过程,即Foo对象的产生过程
obj = self.__new__(self)
self.__init__(obj, *args, **kwargs)
obj.__dict__={'_%s__%s' %(self.__name__,k):v for k,v in obj.__dict__.items()}returnobjclass Foo(object,metaclass=Mymeta): #Foo=Mymeta(...)
def __init__(self, name, age,sex):
self.name=name
self.age=age
self.sex=sex
obj=Foo('lili',18,'male')print(obj.__dict__)
4、基于元类实现单例模式
#步骤五:基于元类实现单例模式#单例:即单个实例,指的是同一个类实例化多次的结果指向同一个对象,用于节省内存空间#如果我们从配置文件中读取配置来进行实例化,在配置相同的情况下,就没必要重复产生对象浪费内存了#settings.py文件内容如下
HOST='1.1.1.1'PORT=3306
#方式一:定义一个类方法实现单例模式
importsettingsclassMysql:__instance=Nonedef __init__(self,host,port):
self.host=host
self.port=port
@classmethoddefsingleton(cls):if not cls.__instance:
cls.__instance=cls(settings.HOST,settings.PORT)return cls.__instanceobj1=Mysql('1.1.1.2',3306)
obj2=Mysql('1.1.1.3',3307)print(obj1 is obj2) #False
obj3=Mysql.singleton()
obj4=Mysql.singleton()print(obj3 is obj4) #True
#方式二:定制元类实现单例模式
importsettingsclassMymeta(type):def __init__(self,name,bases,dic): #定义类Mysql时就触发
#事先先从配置文件中取配置来造一个Mysql的实例出来
self.__instance = object.__new__(self) #产生对象
self.__init__(self.__instance, settings.HOST, settings.PORT) #初始化对象
#上述两步可以合成下面一步
#self.__instance=super().__call__(*args,**kwargs)
super().__init__(name,bases,dic)def __call__(self, *args, **kwargs): #Mysql(...)时触发
if args or kwargs: #args或kwargs内有值
obj=object.__new__(self)
self.__init__(obj,*args,**kwargs)returnobjreturn self.__instance
class Mysql(metaclass=Mymeta):def __init__(self,host,port):
self.host=host
self.port=port
obj1=Mysql() #没有传值则默认从配置文件中读配置来实例化,所有的实例应该指向一个内存地址
obj2=Mysql()
obj3=Mysql()print(obj1 is obj2 isobj3)
obj4=Mysql('1.1.1.4',3307)#方式三:定义一个装饰器实现单例模式
importsettingsdef singleton(cls): #cls=Mysql
_instance=cls(settings.HOST,settings.PORT)def wrapper(*args,**kwargs):if args orkwargs:
obj=cls(*args,**kwargs)returnobjreturn_instancereturnwrapper
@singleton#Mysql=singleton(Mysql)
classMysql:def __init__(self,host,port):
self.host=host
self.port=port
obj1=Mysql()
obj2=Mysql()
obj3=Mysql()print(obj1 is obj2 is obj3) #True
obj4=Mysql('1.1.1.3',3307)
obj5=Mysql('1.1.1.4',3308)print(obj3 is obj4) #False
涉及到的知识点
exec方法
exec()方法可以执行字符串形式的python代码块
使用方法exec(object, global=None, local=None)
object: 字符串类型的python代码块
global: 代表全局名称空间, 必须是字典, 默认为None, 如传参则表明该代码块在全局名称空间中运行
local: 代表局部名称空间, 可以是任何映射, 默认为None, 如传参则表明该代码块在局部空间中运行
code = '''x = 0
sum = x + y + z
print(sum)'''y= 1z= 2global_dict= {'y': 2, 'z': 3}
local_dict= {'y': 3, 'z': 4}exec(code)'''y = 1
z = 2
x = 0
sum = x + y + z
print(sum)'''
exec(code, global_dict)'''相当于
y = 2
z = 3
x = 0
sum = x + y + z
print(sum)'''
exec(code, global_dict, local_dict)'''相当于
y = 2
z = 3
def exec_func():
y = 3
z = 4
x = 0
sum = x + y + z
print(sum)
exec_func()'''
'''3
5
7'''
__call__
对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
classFoo:def __call__(self, *args, **kwargs):print('实例执行 obj()')
f1=Foo()
f1()#调用Foo类下的__call__()
Foo() #触发__call__()方法,一切接对象
__new__和__init__
代码参考
'''通过自定义元类来实现:
1. 类名首字母必须大写
2. 类中必须有文档注释'''
classMyMeta(type):def __init__(self, class_name, class_bases, class_dict):print(class_name) #chinese
print(class_bases) #(,)
print(class_dict) #{'__module__': '__main__', '__qualname__': 'chinese', 'country': 'China', '__init__': , 'kongfu': }
#类名首字母必须大写
if notclass_name.istitle():raise TypeError('类的首字母必须大写!')#类中必须有注释
if not class_dict.get('__doc__'):raise TypeError('类中必须有文档注释!')#调用type中的__init__方法初始化对象
super().__init__(class_name, class_bases, class_dict)class chinese(object, metaclass=MyMeta): #foo = MyMeta('foo', (object, ) {...})
country = 'China'
def __init__(self, name, gender, age):
self.name=name
self.gender=gender
self.age=agedefkongfu(self):print('降龙十八掌!')'''raise TypeError('类的首字母必须大写!')
TypeError: 类的首字母必须大写!'''
#将类名大写, 再运行
class Chinese(object, metaclass=MyMeta): #foo = MyMeta('foo', (object, ) {...})
country = 'China'
def __init__(self, name, gender, age):
self.name=name
self.gender=gender
self.age=agedefkongfu(self):print('降龙十八掌!')'''raise TypeError('类中必须有文档注释!')
TypeError: 类中必须有文档注释!'''