单例模式
实现单例模式的方式有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>}