python-11 单例模式

单例模式

实现单例模式的方式有4种
1、通过@classmethod实现
2、通过装饰器实现
3、通过重写__new__()实现 (常用重点)
4、通过导入模块实现

通过重写__new__()实现单例模式

  • 定义一个类属性,初始值为None,用来记录单例对象的引用
  • 重写__new__()方法
  • 进行判断,如果类属性是None,把__new__()返回的对线引用保存进去
  • 返回类属性中记录的对象引用
class Singleton(object):
    #记录第一个被创建的对象的引用
    obj = None #类属性 用来记录单例对象的引用
    def __new__(cls, *args, **kwargs):  #重写new方法
        print("这是__new__方法")
        # 判断类属性obj是不是为空 为空说明是第一次创建
        if cls.obj == None:
            print("检测到这是第一次调用__new__方法哦")
            cls.obj = super().__new__(cls)
        else:
            print("检测到这不是第一次调用__new__方法哦")
        return cls.obj
    def __init__(self):
        print("这是__init__方法")

s1 = Singleton()
print(s1)
s2 = Singleton()
print(s2)
----------------------------------------
这是__new__方法
检测到这是第一次调用__new__方法哦
这是__init__方法
<__main__.Singleton object at 0x0000024F3D3B7CB0>
这是__new__方法
检测到这不是第一次调用__new__方法哦
这是__init__方法
<__main__.Singleton object at 0x0000024F3D3B7CB0>

通过导入模块实现单例模式

在同一个文件中 多次导入另一个模块的类 本身就是单例的

from test02 import tesCS as tes01
from test02 import tesCS as tes02
print(tes01, id(tes01))
print(tes02, id(tes02))
----------------------------------
<class 'test02.tesCS'> 1660144925632
<class 'test02.tesCS'> 1660144925632

同一 Python 进程内,不同文件导入同一模块,共享同一个模块对象、同一个类对象、同一个类级单例。
不同进程、不同模块名重复加载、reload 后,通常不共享。

魔法方法/属性

指的是python中__xxx__的函数/属性,表示具有特殊功能的函数或属性

常见的魔法方法

  • __new__():在内存中为对象分配空间并返回对象的引用
  • __init__():初始化对象或给属性赋值(构造函数)
  • __doc__:类的描述信息
  • __module__:表示当前操作对象所在的模块
  • __class__:表示当前操作对象所在的类
  • __str__():对象的描述信息
  • __del__():删除对象(析构函数)
  • __call__():使一个实例对象成为一个可调用对象
  • __dict__:返回对象具有的属性和方法

__doc__

class TesCS:
    """我是类的描述信息哦"""  #只有多行注释才能被读取 单行无效
    def testcs(self):
        """我是方法的描述信息哦"""
        print("我是测试类哦")

print(TesCS.__doc__)
print(TesCS.testcs.__doc__)
--------------------------
我是类的描述信息哦
我是方法的描述信息哦

__module__/__class__

import test02
t = test02.TesCS()

print(t.__module__)
print(t.__class__)
---------------------------------
test02
<class 'test02.TesCS'>

__str__()

如果不定义 str(),print(obj) 通常输出类似:<__main__.User object at 0x000001>也就是类名和内存地址,不太可读。

class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"{self.name} ({self.age}岁)"

print(User("Alice", 30))  # Alice (30岁)

用来定义对象的“用户友好字符串表示”。
当你把对象转成字符串、打印对象时,Python 会调用它。
Python 的 str() ≈ Java 的 toString() 都是用来定义“这个对象转换成字符串时应该长什么样”

class User {
    String name;
    int age;

    User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return name + " (" + age + "岁)";
    }
}

System.out.println(new User("Alice", 30)); // Alice (30岁)

__repr__()的区别
__str__()面向用户,__repr__()面向开发者/调试。

class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"{self.name} ({self.age}岁)"

    def __repr__(self):
        return f"User(name={self.name!r}, age={self.age!r})"

u = User("Alice", 30)

print(u)        # Alice (30岁)       -> 调用 __str__
print(repr(u))  # User(name='Alice', age=30) -> 调用 __repr__
print([u])      # [User(name='Alice', age=30)] -> 列表里用 __repr__

几个关键规则:
str(obj) 优先调用 __str__()
如果没有__str__()str(obj) 会退回到 __repr__()
repr(obj) 只调用 __repr__(),不会用 __str__()
容器打印,比如 print([obj])print({"k": obj}),里面的对象用的是__repr__(),不是 __str__()
__str__()必须返回字符串,否则会报 TypeError。

实际用途

  • 让日志更好读:logging.info("用户: %s", user)
  • 让异常信息更清楚:raise ValueError(f"无效用户: {user}")
  • 让界面/CLI 输出更友好
  • 让单例对象打印时显示有意义的信息,而不是内存地址

__call__()

使一个实例对象成为一个可调用对象
可调用对象:可调用对象(callable) 就是:能像函数一样用 () 调用的对象。
也就是说,只要你能写:obj() 并且不报 TypeError: 'X' object is not callable,那 obj 就是可调用对象。
可以用内置函数 callable()来进行判断

callable(obj)  # True 或 False
def f():
    pass

print(callable(f))      # True
print(callable(print))  # True
print(callable(123))    # False
print(callable("abc"))  # False
print(callable([]))     # False

Python 里可调用对象很多:

  • 普通函数、lambda
  • 内置函数,比如 print、len
  • 类的方法、实例方法、类方法、静态方法
  • 类本身
  • 实现了 call 方法的实例
  • 生成器函数
  • 异步函数等
    其中特别注意:类本身也是可调用对象。
class User:
    pass

print(callable(User))  # True

u = User()  # 调用类,创建实例

只要在类里实现 __call__,这个类的实例就可以像函数一样调用:

class Adder:
    def __init__(self, n):
        self.n = n

    def __call__(self, x):
        return self.n + x

add5 = Adder(5)

print(callable(add5))  # True
print(add5(10))        # 15

这里 add5 不是函数,它是一个对象,但因为实现了 call,所以可以写 add5(10)。

底层原理:

当你写:obj(...) 时Python 实际会尝试调用:type(obj).__call__(obj, ...)
函数对象:函数类型实现了 call
类对象:type 实现了 call,所以类可以调用,用来创建实例
普通实例:如果它的类定义了 call,它就可以调用

__dict__

class TesCS:
    """我是类的描述信息哦"""
    name = "张三"
    age = 18
    def testcs(self):
        """我是方法的描述信息哦"""
        print("我是测试类哦")
        return "我是测试类返回值哦"
    def eat(self):
        print("喜欢吃烧鹅")
    def __str__(self):
        return "这是一个测试类"

print(TesCS.__dict__)
---------------------------------------------
{'__module__': '__main__',
 '__firstlineno__': 1, 
'__doc__': '我是类的描述信息哦',
 'name': '张三', 
'age': 18, 
'testcs': <function TesCS.testcs at 0x0000019AE7B73A60>,
 'eat': <function TesCS.eat at 0x0000019AE7B78680>, 
'__str__': <function TesCS.__str__ at 0x0000019AE7B78D60>, 
'__static_attributes__': (), 
'__dict__': <attribute '__dict__' of 'TesCS' objects>, 
'__weakref__': <attribute '__weakref__' of 'TesCS' objects>}

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容