侧边栏壁纸
  • 累计撰写 218 篇文章
  • 累计创建 59 个标签
  • 累计收到 5 条评论

Python OOB - 从单例模式认识 new 和 init

barwe
2021-06-29 / 0 评论 / 0 点赞 / 539 阅读 / 2,470 字
温馨提示:
本文最后更新于 2022-07-14,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

一个简单的单例模式:

class Singleton(object):
    
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, 'instance'):
            cls.instance = super().__new__(cls)
        return cls.instance
    
    def __init__(self, name, *args, **kwargs):
        self.name = name
        ...
        
args = []
kwargs = {}
x = Singleton('xx', *args, **kwargs)
y = Singletom('yy')
id(x) == id(y) #=> True

Python 中可以通过 __new__ 方法实现单例模式。

在上面的例子中,调用 Singleton('xx') 创建单例对象的具体步骤是:

  1. 调用 Singleton 类的静态 __new__ 方法创建一个 Singleton 实例
    1. 将参数列表 ('xx', *args, **kwargs) 传递给 __new__ 方法,所以最好给 __new__ 方法加上 (cls, *args, **kwargs) 参数以保证其能接收所有传入的实参,cls 表示这是一个类的静态方法,方法内的所有操作都不能基于实例,因为实例还未创建。这里 cls 指的就是类 Singleton 本身
    2. hasattr 函数用来判断类或者实例上是否定义了某个属性(类属性或者实例属性),这里通过判断类属性 instance 是否存在,来确定该类是否存在实例。类属性 instance 不存在意味着该类尚未实例化出任何一个对象,此时调用父类的 __new__ 方法构造一个实例并将其保存在类属性 instance 中,当下次尝试实例化该类时,发现类属性 instance 已经存在,此时就会直接返回第一次构造的实例,即 cls.instance
    3. __new__ 方法构造了一个指定类的实例,并且需要返回这个实例。需要注意的是,只有 __new__ 方法返回当前类的实例时才能正确调用接下来的 __init__ 方法,不返回或者返回其它类的实例都不会调用自定义的 __init__ 方法
    4. 因为当前类继承了父类(直到继承 object 类),所以当前类的 __new__ 方法构造的(未初始化的)实例实际上与父类的(初始化后的)对象拥有相同的属性和方法,因此 __new__ 方法返回的实例是通过父类的 __new__ 方法构造出的实例,__new__ 方法的第一个参数指定了返回的实例的类型,通常情况下该参数应该是 cls,以确保 __new__ 方法返回的是当前类的一个实例,这样才能成功执行自定义的 __init__ 方法。
  2. 调用 Singleton 实例的 __init__ 方法完成初始化
    1. __init__ 方法在 __new__ 方法返回该类的实例后自动执行(__new__ 方法不返回或者返回其它类的实例时则不会执行)
    2. __init__ 方法的第一个参数是 self,表示它是一个实例的方法,这个实例就是__new__ 方法返回的实例;self 代表了 实例 或者叫 对象
    3. 参数列表 ('xx', *args, **kwargs) 会再次传递给 __init__ 方法,可以设计自己设计 __init__ 方法的形参,只要保证所有的实参能被正确接收即可。
    4. 对当前的实例进行初始化,添加额外的属性等。正是 __init__ 方法给实例添加了额外的属性,使得此实例比父类的对象内容更多,结构更复杂,达到抽取公共属性和方法的目的。

__new__ 方法用于 构造实例,它是类的静态方法,可以使用类的静态方法和属性,也可以定义类的属性。通常 __new__ 方法会调用父类的 __new__ 方法并指定 cls 来构造一个本类的实例。__new__ 方法最后还需要返回本类的实例,才能使 __init__ 方法自动调用。

如果 __new__ 方法返回的是其它类的实例,__init__ 方法不会被调用,最终返回给用户的对象也是其他类的实例,并且这个实例是不完整的,因为它的 __init__ 方法没有被正确调用,看这个例子(https://zhuanlan.zhihu.com/p/279393180):

class B(object):
    def __new__(cls):
        int("B __new__ called")
        return super().__new__(cls)

    def __init__(self):
        print("B __init__ called")
        self.attr='B'

    def test(self):
        print("B test")

class C(object):
    def __new__(cls):
        print("C __new__ called")
        return super().__new__(B)
    def __int__(self):
        print("C __init__ called")
        self.attr="C"

    def test(self):
        print("C test")

c=C()
c.test()
print(c.attr)

打印结果为:

C __new__ called
B test
Traceback (most recent call last):
  File ...
    print(c.attr)
AttributeError: 'B' object has no attribute 'attr'

C.__new__ 返回了 super().__new__(B),表示使用 C 类的父类(这里是 object)构造了一个实例,并将其转换为为 B 类型后返回。最终用户取得的 c 实际上是 B 类的一个实例,并且构造这个实例时没有调用其 __init__ 方法。调用 c.test() 实际上调用的是 B 类实例的 .test() 方法。

因为 C.__new__ 没有正确返回 C 类的实例,导致该实例不会自动执行 C 类中为实例初始化准备的 __init__ 方法,同时该实例也不会自动执行 B 类的 __init__ 方法,导致该实例实际上没有定义 attr 属性,因此访问 c.attr 报错。

0

评论区