全网最细总结Python中的元类以及单例模式的实现

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__ 主要还是使用父类来实现的功能,这里自定义元类只是让大家理清楚代码执行流程,方便大家在以后的开发中自己定制元类的需要

基于元类实现的单例模式

单例模式的优点

由于对象只有唯一的实例,因此从根本上避免了重复创建对象造成的时间和空间上的开销,也避免了对资源的多重占用

单例模式的应用场景

  1. 数据库的连接池对象和配置对象
  2. 项目中的日志操作使用单例模式,因为共享的日志文件处于打开状态,只能有一个实例去操作它

单例模式的实现方式

上文中我们讲到了基于__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__方法的实现和基于元类的实现,码字不易,博主本人也是看了其他博主的博文再结合自己不到位的理解总结的,如有错误之处欢迎大家在评论区指出

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

code_lover_forever

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值