python元类编程_Python 元类编程

一切皆对象

函数是对象

在Python中,一切皆为对象。我们之前常用的函数,本身也是一个实例化对象。

#==== 函数也是对象 ====

deffunc():pass

print(func.__class__)#==== 我们还可以为函数对象进行增加属性的操作:虽然没人这样做 ====

func.x= "随便写的: x"func.y= "随便写的: y"

print(func.x)print(func.y)#==== 执行结果 ==== Ps:可以看到,函数是类function的实例对象。这充分验证了Python中一切皆对象的概念。

"""

随便写的: x

随便写的: y"""

类也是对象

我们使用class定义出的类,本身也是某一个类的实例对象... 我知道这可能很颠覆三观。但是事实如此...

type类是所有类的元类,即metaclass...

#==== 类也是对象 ====

classFoo(object):pass

print(Foo.__class__)print(object.__class__)print(dict.__class__)print(list.__class__)#==== 执行结果 ==== Ps:可以看见,不管是自定义的类,还是Python自带的类,甚至是object类,都是type类的实例对象。

"""

"""

什么是元类

经过上面的例子,我们可以看到所有类都是由type类进行实例化而得到的,因此。元类即是对类进行实例化的类。

元类的作用:

1.用于生产出类对象

2.用于生产出类对象的实例对象

浅析class内部机制

一个类定义后发生的4件事

我们使用class定义一个类,实际底层都是由type类进行实例化的。那么对于一个类定义后发生的事情总体有以下四步:

1.拿到类名。

2.拿到类的基类。

3.执行类体代码,创建类的局部命名空间。

4.调用元类,默认为type类,得到实例化出的类对象。

#==== 常规定义类 ====

classPeople(object):def __init__(self, name, age):

self.name=name

self.age=agedefsay(self):print("{0}:{1}".format(self.name, self.age))#==== 测试 ====

print(People.__dict__)

p1= People("Yunya",18)

p1.say()#==== 执行结果 ====

"""{'__module__': '__main__', '__init__': , 'say': , '__dict__': , '__weakref__': , '__doc__': None}

Yunya:18"""

常规定义类

#==== 一个类定义后发生的4件事 ====

#类有三大特征:

#1、类名

class_name = "People"

#2、类的基类

class_bases = (object,) #

#3、执行类体代码拿到类的命名空间

class_dic = {} #

class_body = """def __init__(self,name,age):

self.name = name

self.age = age

def say(self):

print("{0}:{1}".format(self.name,self.age))"""

exec(class_body,{},class_dic) #位置1:执行的代码,位置2:全局命名空间,位置3,执行代码期间出现的局部命名空间的变量全部存放于此

print(class_dic) #该字典中存放了类中的变量名对应的内存地址。

#4、调用元类

People =type(class_name,class_bases,class_dic)#==== 测试 ====

p1= People("Yunya",18)

p1.say()#可以成功执行!

#==== 执行结果 ==== Ps:虽然可以用这种方式来实例化出类对象,但是我们并不推荐这样做。相比于直接使用class机制,这样做法显得复杂并且对比class_dic与People.__dict__来看明显使用class机制会完善的多。

"""{'__init__': , 'say': }

Yunya:18"""

一个类定义后发生的4件事

#exec:三个参数

#参数一:包含一系列python代码的字符串

#参数二:全局作用域(字典形式),如果不指定,默认为globals()

#参数三:局部作用域(字典形式),如果不指定,默认为locals()

#可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中

g={'x':1,'y':2}

l={}exec('''global x,z

x=100

z=200

m=300''',g,l)print(g) #{'x': 100, 'y': 2,'z':200,......}

print(l) #{'m': 300}

exec参数补充

自定义元类

元类如何生产出类对象

我们继续探讨关于上节标黄的地方。元类如何生产出类对象。

元类生产出类对象会经过两个步骤:

1.调用 元类自己的__new__方法(创建出空的对象)

2.调用 元类自己的__init__方法(将空对象构造为类对象,可以理解为为其__dict__填充数据)

注意:

__new__方法其实是在__init__方法之前执行的。因为我们之前在学习__init__方法中可以看到参数self是一个空对象,

这个空对象实际上就是__new__制造出的。

#==== 元类如何生产出类对象 ====

class MetaClass(type): #

"""自定义元类"""

def __new__(cls, *args, **kwargs):"""cls ---> 类本身(空的)

args ---> ("类名",(类的继承关系,),{类的局部命名空间})

kwargs ---> 空"""

print("MetaClass.__new__ --> cls \n",cls)print("MetaClass.__new__ --> args \n",args)print("MetaClass.__new__ --> kwargs \n",kwargs)#return super().__new__(cls,*args,**kwargs) # 注意,此时必须将属性全部传入。因为现在的对象是个空对象,或者可以采取下面的方式

return type.__new__(cls,*args,**kwargs) #让父类 type 为我们造出空对象

def __init__(self,*args,**kwargs):"""cls ---> 类本身(空的)

args ---> ("类名",(类的继承关系,),{类的局部命名空间})

kwargs ---> 空"""

print("MetaClass.__init__ --> self \n",self)print("MetaClass.__init__ --> args \n",args)print("MetaClass.__init__ --> kwargs \n",kwargs)

super(MetaClass, self).__init__(*args,**kwargs) #让父类 __init__ 为我们将空对象转换为类对象

class People(object,metaclass=MetaClass):def __init__(self, name, age):

self.name=name

self.age=agedefsay(self):print("{0}:{1}".format(self.name, self.age))#==== 执行结果 ====

"""MetaClass.__new__ --> cls

MetaClass.__new__ --> args

('People', (,), {'__module__': '__main__', '__qualname__': 'People', '__init__': , 'say': })

MetaClass.__new__ --> kwargs

{}

MetaClass.__init__ --> self

MetaClass.__init__ --> args

('People', (,), {'__module__': '__main__', '__qualname__': 'People', '__init__': , 'say': })

MetaClass.__init__ --> kwargs

{}"""

类如何生产出类对象

元类如何生产出类对象的实例对象

当造好的类对象试图使用 类名() 的方式进行实例化,会发生以下几件事。

1.类对象调用元类下的 __call__方法。

2.元类下的__call__方法会去调用类对象下的__new__方法创建空对象

3.元类下的__call__方法会去调用类对象下的__init__方法将空对象转换为类对象的实例对象

4.元类下的__call__方法将返回造好的类对象的实例对象。

关于__call__方法,试图使用 名字()的方式将触发其实例化出自己类中的 __call__方法,有则执行,没有则抛出异常。

比如我们定义的一个函数,使用函数名()的方式就代表是去调用元类type中的__call__方法,然后再到function类中去执行其__new__方法以及__init__方法。

#==== 有__call__方法 ====

classPeople(object):def __init__(self, name, age):

self.name=name

self.age=agedefsay(self):print("{0}:{1}".format(self.name, self.age))def __call__(self, *args, **kwargs):print("调用了__call__")#==== 测试 ====

p1= People("Yunya",18)

p1()#==== 执行结果 ==== Ps:没有抛出异常了。

"""调用了__call__"""

有__call__方法

#==== 无__call__方法 ====

classPeople(object):def __init__(self, name, age):

self.name=name

self.age=agedefsay(self):print("{0}:{1}".format(self.name, self.age))#==== 测试 ====

p1= People("Yunya",18)

p1()#==== 执行结果 ==== Ps:由于People类中没有定义__call__方法,则其实例化对象不能使用 名字() 的方式进行调用

"""Traceback (most recent call last):

File "C:/Users/Administrator/PycharmProjects/learn/反射与自省.py", line 15, in

p1()

TypeError: 'People' object is not callable"""

无__call__方法

#==== 元类如何生产出类对象的实例对象 ====

class MetaClass(type): #

"""自定义元类"""

def __call__(self, *args, **kwargs):"""self --> 类对象本身(空的)

args --> 传入的参数。 ('Yunya', 18)

kwargs --> 空的,{}"""

## 构造出空对象

obj = self.__new__(self, *args, **kwargs)print(obj) #<__main__.people object at>

print("__new__过后的对象的__dict__",obj.__dict__)#将空对象转换为类对象People的实例对象

self.__init__(obj,*args,**kwargs)print("__init__过后的对象的__dict__",obj.__dict__)#将类对象People的实例对象进行返回

returnobjclass People(object,metaclass=MetaClass):def __new__(cls, *args, **kwargs):return object.__new__(cls)def __init__(self, name, age):

self.name=name

self.age=agedefsay(self):print("{0}:{1}".format(self.name, self.age))

p1= People("Yunya",18) #相当于调用元类下的 __call__

print(p1.name)print(p1.age)#==== 执行结果 ====

"""<__main__.people object at>

__new__过后的对象的__dict__ {}

__init__过后的对象的__dict__ {'name': 'Yunya', 'age': 18}

Yunya

18"""

元类如何生产出类对象的实例对象

实例对象与类对象的查找顺序

类对象的属性查找顺序

现在该介绍终极版的属性查找顺序了。

细心的朋友应该发现,为什么类对象可以调用元类的 __call__, 而类对象的实例对象却不能?这里其实是有区别的。这里先介绍类对象的属性查找顺序。

类本身 --> 父类 --> object类 --> 元类

#==== 类对象的属性查找顺序 ====

class MetaClass(type): #

"""自定义元类"""name= "MetaClass"

classA(object):#name = "A"

pass

classB(A):#name = "B"

pass

class C(B,metaclass=MetaClass):pass

print(C.name)#==== 执行结果 ====

"""MetaClass"""

类对象的属性查找顺序

实例对象的属性查找顺序

实例对象还是通过MRO列表进行找。但是并不会找到元类中去

实例对象本身 --> 实例化出自己的类 --> 父类 --> object类

所以!实例对象是拿不到元类中的 __call__的,故不能进行 名字()的使用!

#==== 实例对象属性查找顺序 ====

class MetaClass(type): #

"""自定义元类"""name= "MetaClass"

classA(object):#name = "A"

pass

classB(A):#name = "B"

pass

class C(B,metaclass=MetaClass):passc1=C()print(c1.name)#==== 执行结果 ====

"""Traceback (most recent call last):

File "C:/Users/Administrator/PycharmProjects/learn/反射与自省.py", line 19, in

print(c1.name)

AttributeError: 'C' object has no attribute 'name'"""

实例对象属性查找顺序

练习题

类的命名规则检测

class MetaClass(type): #

"""自定义元类"""

def __init__(self,*args,**kwargs):if notargs[0].istitle():raise NameError("类名必须是大写!")

super(MetaClass,self).__init__(*args,**kwargs)class student(object,metaclass=MetaClass):pass

#==== 执行结果 ====

"""Traceback (most recent call last):

File "C:/Users/Administrator/PycharmProjects/learn/元类编程.py", line 11, in

class student(object,metaclass=MetaClass):

File "C:/Users/Administrator/PycharmProjects/learn/元类编程.py", line 6, in __init__

raise NameError("类名必须是大写!")

NameError: 类名必须是大写!"""

类的命名规则检测

类的文档信息检测

class MetaClass(type): #

"""自定义元类"""

def __init__(self,*args,**kwargs):if "__doc__" not in args[2]:raise SyntaxError("{0}类必须要有文档注释".format(args[0])) #SyntaxError 语法错误

super(MetaClass,self).__init__(*args,**kwargs)class student(object,metaclass=MetaClass):pass

#==== 执行结果 ====

"""Traceback (most recent call last):

File "C:/Users/Administrator/PycharmProjects/learn/元类编程.py", line 11, in

class student(object,metaclass=MetaClass):

File "C:/Users/Administrator/PycharmProjects/learn/元类编程.py", line 6, in __init__

raise SyntaxError("{0}类必须要有文档注释".format(args[0])) # SyntaxError 语法错误

SyntaxError: student类必须要有文档注释"""

类的文档信息检测

基于元类实现单例模式

#单例:即单个实例,指的是同一个类实例化多次的结果指向同一个对象,用于节省内存空间#如果我们从配置文件中读取配置来进行实例化,在配置相同的情况下,就没必要重复产生对象浪费内存了#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) #进行 Mysql.__init__(settings.HOST,settings.PORT)) 并赋值

return cls.__instanceobj1=Mysql('1.1.1.2',3306)

obj2=Mysql('1.1.1.2',3306)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

基于元类实现单例模式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值