python猴子补丁:修改类的__new__
1 python的单例模式(python的类中,_属性是protected变量,__属性是private变量,一般的属性就是public变量,概念上和java类似,但有区别,python的私有变量只是概念上的,可以通过dir(类名)打印获取私有变量,本质上可以通过_类 _ _属性访问):
class A:
_obj = None
def __new__(cls, *args, **kwargs):
if not cls._obj:
cls._obj = super().__new__(cls)
return cls._obj
def __init__(self, x):
self.x = x
a = A(1)
print(id(a), a.x)
b = A(2)
print(id(b), b.x)
#结果如下,a和b实例对象共用一个内存空间,且实例对象的属性不同
17648440 1
17648440 2
2 如果希望动态的为类增加单例模式,也就是需要重写类的__new__方法,就需要动态修改类的实例方法,需要使用tpyes模块,使用types.MethodType,将函数名和实例对象传入,进行方法绑定(python的类绑定实例属性直接:类.属性=xxx即可,python动态语言的美妙)

查看MethodType的源码可知,传入参数function(callable的对象,python中函数和类都是callable的)和对象即可
import types
class A:
_obj = None
def __init__(self, x):
self.x = x
def new(cls, *args, **kwargs):
if not cls._obj:
cls._obj = super().__new__(cls)
return cls._obj
A.__new__ = types.MethodType(new, A)
a = A(1)
print(id(a), a.x)
b = A(2)
print(id(b), b.x)
#报错:
RuntimeError: super(): __class__ cell not found
在类外部定义的函数中,不能在没有参数的情况下使用super(), __class__单元格super()所依赖的元素仅提供给class主体中定义的功能。从super() 文档可知:
零参数形式仅在类定义内起作用,因为编译器会填写必要的详细信息以正确检索要定义的类,以及为常规方法访问当前实例。
super使用的方式:
-
super(类名,对象名).方法():既可在类的内部也可在类的外部使用,如继承了父类的子类的:
super(子类名,self). _ _ init _ _(父类init的参数) -
父类类名.方法名(self):A.b(self) 既可在内部也可也在外部使用
-
super().xxx():只能在类的内部,如:super()._ _ init _ _()
修改如下:
import types
class A:
_obj = None
# def __new__(cls, *args, **kwargs):
# if not cls._obj:
# cls._obj = super().__new__(cls)
# return cls._obj
def __init__(self, x):
self.x = x
def new(cls, *args, **kwargs):
if not cls._obj:
# cls._obj = super(A, cls).__new__(cls)
# 将super的第一个参数改为cls,可以作为类名
cls._obj = super(cls, cls).__new__(cls)
return cls._obj
A.__new__ = types.MethodType(new, A)
a = A(1)
print(id(a), a.x)
b = A(2)
print(id(b), b.x)
# 结果如下:可见内存空间地址一致,且实例的属性改变了
28396392 1
28396392 2
另一种情况:
import types
class A:
_obj = None
# def __new__(cls, *args, **kwargs):
# if not cls._obj:
# cls._obj = super().__new__(cls)
# return cls._obj
def __init__(self, x):
self.x = x
class B:
def __init__(self, x):
self.x = x
def new(cls, *args, **kwargs):
# cls._obj=None
if not cls._obj:
# cls._obj = super(A, cls).__new__(cls)
# 将super的第一个参数改为cls,可以代替所有的子类
cls._obj = super(cls, cls).__new__(cls)
return cls._obj
A.__new__ = types.MethodType(new, A)
B._obj = None
B.__new__ = types.MethodType(new, B)
a = A(1)
print(id(a), a.x)
b = A(2)
print(id(b), b.x)
print(id(a), a.x)
c = B(5)
d = B(8)
print(id(c), c.x)
print(id(d), d.x)
print(id(c), c.x)
# 结果如下:对于A来说,实例共用的一个内存空间,所以实例化b后,a也是指向的
# 同一个内存空间,所以x的值相同,对于B来说,因为外部的全局变量B._obj,
# 是类共用的类属性(不是每个实例对象单独开辟的空间),所以实例对象可以
# 成为单例模式,x均是8
# 因为在python中,类只会开辟1次空间,实例一般情况下会开辟多个空间
28659064 1
28659064 2
28659064 2
28659256 8
28659256 8
28659256 8
# 如果做如下修改
def new(cls, *args, **kwargs):
cls._obj=None
if not cls._obj:
# cls._obj = super(A, cls).__new__(cls)
# 将super的第一个参数改为cls,可以代替所有的子类
cls._obj = super(cls, cls).__new__(cls)
return cls._obj
# 将_obj存入类的__new__方法中,外部的B._obj = None去掉,这种方式
# 每次生成实例对象,都会生成1个新的对象
# 重新执行
# 结果中,B的两个实例对象的x和内存地址均改变(包括A类,因为A类
# 和B类都是使用的同一个__new__)
26168672 1
26168840 2
26168672 1
26168888 5
26168936 8
本文探讨了Python中的单例模式实现,通过修改类的__new__方法实现。首先介绍了Python类的访问权限,并展示了标准的单例模式实现。接着,讲解了如何使用types模块的MethodType动态修改类的方法,以实现单例。在过程中,指出了super()在类外部使用时的限制,并提供了正确的使用方式。最后,通过示例展示了动态修改__new__方法对单例模式的影响,以及不同场景下的内存分配和属性变化情况。
580

被折叠的 条评论
为什么被折叠?



