Python中的元类
__new__与__init__方法
在python中可以基于类创建对象
问:__new__
和__init__
的区别?
1.
__new__
是在实例创建之前被调用的,用于创建实例然后返回该实例对象
2.
__new__
至少要有一个参数cls
代表当前类,此参数在实例化时由Python
解释器自动识别
3.
__new__
必须要有返回值,返回的是实例化出来的实例(如果__new__
没有返回当前类的实例,那么当前类的__init__
方法是不会被调用的)
class MyType(object):
def __init__(self, *args, **kwargs):
print(f"我是初始化方法,self的id为:{id(self)}")
self.para1 = args[0]
self.para2 = kwargs['para2']
def __new__(cls, *args, **kwargs):
print(f"我是构造方法,先执行的是我,cls的id为:{id(cls)}")
new_obj = super(MyType, cls).__new__(cls)
print(f"创建的对象id为:{id(new_obj)}")
return new_obj
my_class = MyType("参数1", para2='参数2')
print(my_class.para1)
print(my_class.para2)
print(f"实例化出来的对象my_class的id为:{id(my_class)}")
上述代码执行流: 结合每一步打印出来的id值进行观察
构造方法中的两种return方式
return object.__new__(cls)
return super(父类,cls).__new__(cls)
基于__new__方法实现的单例模式
有了上述的基础知识我们再来看一下面试中的常见问题:
单例模式的实现
class Singleton(object):
instance = None # 定义一个类变量
def __init__(self, name, age):
self.name = name
self.age = age
def __new__(cls, *args, **kwargs):
if not cls.instance:
cls.instance = object.__new__(cls) # 创建空对象
return cls.instance
obj1 = Singleton('柯基', 2)
obj2 = Singleton('萨摩耶', 3)
print(id(obj1))
print(id(obj2)) # 两个对象的id值相同,保证每次实例化对象用的都是同一个内存地址
# 猜一下打印的结果,因为实例化出来的对象指向同一块内存地址,obj1中的变量name和age被obj2给覆盖掉了
print(f"我叫 {obj1.name}, 我今年 {obj1.age} 岁了")
注: 该单例模式只是最简单的一个实现,并不是线程安全的,不过是理解单例模式原理最简单的例子,用于面试已经足够
类创建对象,谁创建类
问:类是由谁创建的?
类默认是由type创建的,Python使用type类来创建其他的类,任何类在内存里就是一个type类的对象
这是我们熟悉的基于传统方式创建一个类以及实例化这个类的对象
class Dog(object):
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
print(f"我是{self.name}, 我今年 {self.age} 岁了")
keji = Dog("柯基", 3)
keji.introduce()
这是我们基于type
方式动态创建一个类,最终效果和上面完全一样
def introduce(self):
print(f"我是{self.name}, 我今年 {self.age} 岁了")
def __init__(self, name, age):
self.name = name
self.age = age
Dog = type("Dog", (object,), {'introduce': introduce, '__init__': __init__})
keji = Dog("柯基", 3)
keji.introduce()
type(class_name, parent, class_dict)
第一个参数:要创建的类的名称
第二个参数:继承的父类集合,Python支持多重继承
第三个参数:方法名字典,{‘类里面的方法名称’: ‘你定义好的函数的名称’}
注:在实际开发中你几乎不用写基于type创建类的这种创建方式,这里只是做个简单的介绍,为了后序我们能更好的理解元类
问:类默认是由type创建,那怎么让一个类的创建可以由我们自己指定呢?
基于
type
构造元类,在定义一个类时,如果没有给一个类指定父类,那么默认的父类是object
,如果没有给一个类指定元类那么默认的类是type
,通过自定义的元类我们可以改变一个类的默认行为
一个
metaclass
就是一个用来创建其他class
的类
# 这里的 MyType 是我们自定义的元类,元类的构建需要继承 type 类,使用__new__方法去定义元类的构造
# 其实原理就是基于继承和重写(继承父类 type 的__new__和__init__进行构造和初始化)
class MyType(type):
def __new__(mcs, *args, **kwargs):
# 创建"类"时调用, 可以在创建"类"时进行扩展
print(f"我是元类构造方法,mcs的id为: {id(mcs)}")
new_cls = super().__new__(mcs, *args, **kwargs) # 这里调用的是父类的构造方法
print(f"新创建的 new_class 的id为: {id(new_cls)}")
return new_cls
def __init__(cls, *args, **kwargs):
# 创建"类"时调用, 可以在创建"类"时进行扩展
print(f"我是初始化方法, cls的id为: {id(cls)}")
super().__init__(*args, **kwargs) # 这里用父类的方法的初始化方法
# Foo类实际一个类但同时也是MyType创建的对象: 创建类时调用元类的__new__方法来创建,__init__方法来初始化
class Foo(object, metaclass=MyType):
pass
print(f"Foo类的id为: {id(Foo)}")
下图是上述代码执行流分析
注意:使用同一个颜色标注出来的表示是同一个对象
元类中的__call__方法
问:__call__方法是何时调用的?
class A(object):
def __call__(self, *args, **kwargs):
print("何时调用call方法")
# 类名+(): 实例化出来的一个对象,实际执行的是__new__和__init__方法,此时不调用类中定义的call方法
a = A() # A中没有__new__和__init__就执行父类的__new__和__init__方法
# 对象+(): 此时调用才是A类中定义的__call__方法
a()
代码示例
问:__call__方法内部的执行流程是怎样的,为什么实例化一个对象时先执行__new__方法再执行__init__方法?
1.元类的
__call__
会先调用type
内的__new__
方法创造一个空对象
2.元类的__call__
函数会接着调用type
内的__init__
方法初始化
3.元类的__call__
会返回一个初始化好的对象
废话不多说,上代码
class MyType(type):
def __new__(mcs, *args, **kwargs):
print(f"元类的构造方法, mcs的id为: {id(mcs)}")
new_cls = super().__new__(mcs, *args, **kwargs)
print(f"新构建类的new_cls的id为: {id(new_cls)}")
return new_cls
def __init__(cls, *args, **kwargs):
print(f"元类的初始化方法, cls的id为: {id(cls)}")
super().__init__(*args, **kwargs)
def __call__(cls, *args, **kwargs):
print(f"自定义__call__方法,啥都不干使用父类的__call__方法,注意看__call__方法的执行流, cls的id为{id(cls)}")
return super().__call__(*args, **kwargs) # 使用type中的_call__
class Foo(object, metaclass=MyType):
def __init__(self, name):
print(f"Foo类的初始化方法, self的id: {id(self)}")
self.name = name
def __new__(cls, *args, **kwargs):
print(f"Foo类的构造方法,cls的id: {id(cls)}")
new_obj = object.__new__(cls)
print(f"新创建的对象new_obj的id为: {id(new_obj)}")
return new_obj
def __call__(self, *args, **kwargs):
print("使用Foo实例化出来的对象的调用")
v1 = Foo('SB')
print(v1)
print(v1.name)
v1()
分析上述代码执行流程,主要理解
__call__
方法的执行流,看图片,下面都给大家画出来了,这是我能想到的最直观的表述方式了
如果没有给一个类指定元类那么默认的类是type
,在定义Foo类时,不指定元类其实默认的元类就是type类,我们在这里自定义的元类中的__new__
,__init__
, __call__
主要还是使用父类来实现的功能,这里自定义元类只是让大家理清楚代码执行流程,方便大家在以后的开发中自己定制元类的需要
基于元类实现的单例模式
单例模式的优点
由于对象只有唯一的实例,因此从根本上避免了重复创建对象造成的时间和空间上的开销,也避免了对资源的多重占用
单例模式的应用场景
- 数据库的连接池对象和配置对象
- 项目中的日志操作使用单例模式,因为共享的日志文件处于打开状态,只能有一个实例去操作它
单例模式的实现方式
上文中我们讲到了基于__new__实现的单例模式,现在我们使用元类实现单例模式
这里和上文的区别是,我们现在使用我们自定义的元类创建的类,然后再基于该类创建的对象,保证单例
简单一句话就是: 基于我们自定义的元类创建出来的类实例化出来的对象
保证单例
Python
是面向对象的编程语言,在Python
中一切皆对象,对象是用过类来创建的,而类这样的对象
是通过元类来创建的
class SingletonMeta(type): # 继承type并重写type的构造方法,初始化方法,__call__方法
"""自定义单例元类"""
def __init__(cls, *args, **kwargs):
"""
:本质还是使用父类的初始化方法进行初始化,不过我们可以自定义一些操作,这里我们定义了一个类变量
"""
cls.__instance = None
super().__init__(*args, **kwargs)
def __new__(mcs, *args, **kwargs):
"""
:本质还是使用父类的构造方法来构造,在构造前我们可以自定义一些操作,这里我们打印一句话
"""
print("先执行我来构造类")
return super().__new__(mcs, *args, **kwargs)
def __call__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = super().__call__(*args, **kwargs)
return cls.__instance
class President(metaclass=SingletonMeta): # 先执行我们自定义的元类中的__new__和__init__构造和初始化这个类
pass
# President类是 SingletonMeta 元类实例化出来的一个"对象",由上文可知"对象+()"即可执行 SingletonMeta 中定义的__call__方法
A = President()
B = President()
print(id(A))
print(id(B))
相信经过上述代码执行流的分析,这里的单例模式的代码执行流简直就是小菜一碟,大家也可以使用debug模式来验证一下代码执行流是否和自己理解的一致
总结
上文中主要介绍了__new__
方法,__init__
方法, __call__
方法,以及实现单例模式的两种方法,基于__new__
方法的实现和基于元类的实现,码字不易,博主本人也是看了其他博主的博文再结合自己不到位的理解总结的,如有错误之处欢迎大家在评论区指出