从前我写了一个Singleton课程。该实现是仅由静态方法组成的类-我在__init__
上添加了一个例外,仅出于说明目的,我什至没有__init__
,但以后需要一个:
class A:
x = 3
def __init__(self): raise Exception('Oh no you did not!')
@staticmethod
def m1():
print(A.x)
@staticmethod
def m2(n):
A.y=8
快进几年了,现在我需要创建Singleton的实例-请不要判断:)。要求:
staticmethod
装饰器都需要删除self
参数。__init__
方法解决方案可能在原始类中实现,但我认为这可能是不可能的,因此采用原始类并喷出新类的功能可能是解决方法:
class A:
x = 3 #No need to touch class variable definitions.
def __init__(self): pass #Maybe the init method will be an argument
def m1(self):
print(self.x)
def m2(self,n):
self.y = n
任何可以动态创建内容的解决方案(无论是否为hack)都可以。我目前正在考虑使用inspect
构建它,尽管不确定它是否会成功。
答案 0 :(得分:2)
我不确定这是否足够,但是我认为以下修改可以同时用作您的原始单身人士和普通班级。有很多样板,但它是与类隔离的,而不是使用该类的代码。
class A:
def __init__(self):
pass
def m1(*args):
# A.m1() means args will be empty
if args and isinstance(args[0], A):
self = args[0]
else:
self = A
print(self.x)
def m2(*args):
if isinstance(args[0], A):
self, n = args
else:
self = A
n = args[0]
self.y = n
基本上,您将对每种方法执行以下操作:
staticmethod
装饰器*args
self = A
,否则设置self = args[0]
。*args
的适当元素为每个旧参数手动创建局部变量。例如,A.m1()
产生print(A.x)
,而a = A(); a.mi()
产生print(a.x)
。同样,A.m2(8)
是A.y = 8
,而a.m2(8)
是a.y = 8
。
我不愿意尝试进一步自动化它;与手动更新每种方法相比,您可能会花费更多的时间来确定和尝试处理极端情况。
答案 1 :(得分:2)
很难将所有副作用仅会影响类本身的静态单例类转换为副作用仅会影响实例对象的普通类。但是可以做相反的事情:只需使静态类拥有该普通类的唯一实例,然后将其所有方法的调用和属性访问委托给该静态类,即可将其转换为单例静态类。
元类可以完成这项工作。这个创建了一个特殊的属性_own
来保存其 model 类的实例,为它显式地创建带有适当签名的方法(如果有的话,还会保留文档字符串),只是将对{{ 1}},并将所有属性访问委派给_own
_own
在您的示例中,非单例类将为b:
import inspect
class Singletoner(type):
def __getattr__(self, attr): # delegates attribute accesses
return getattr(self._own, attr)
def __new__(cls, name, bases, namespace, **kwds):
obj = type.__new__(cls, name, bases, namespace)
X = kwds['model'] # the model class is expected with the model keyword
obj._own = X()
for name, func in inspect.getmembers(X, inspect.isfunction):
if name != '__init__':
_trans(name, func, obj) # tranfers all methods other than __init__
return obj
def _trans(name, func, clazz):
def f(*args,**kwargs):
return func(clazz._own, *args, **kwargs)
sig = inspect.signature(func) # copy signature just removing the initial param
parameters = sig.parameters
params = [t[1] for t in list(sig.parameters.items())[1:]]
f.__signature__ = sig.replace(parameters = params)
f.__doc__ = func.__doc__
setattr(clazz, name, f)
其单例委托人可以声明为:
class A:
x = 3
def __init__(self): pass
def m1(self):
print(self.x)
def m2(self, n):
self.y=n
您可以简单地使用它:
class B(metaclass=Singletoner, model=A):
pass
答案 2 :(得分:1)
在我看来,最大的问题似乎是方法调用self.x
而不是A.x
,这将是一个愚蠢的主意,但您表示已修正了漏洞,所以我们可以备份所有类属性的值,更改它们以匹配实例属性,然后调用static方法,然后恢复所有值?如果允许的话,这样的方法可能会起作用:
import types
class A:
x=3
def __init__(self):
pass
@staticmethod
def m1():
print(A.x)
@staticmethod
def m2(n):
A.y = n
def __getattribute__(self, name):
Aattr = getattr(type(self),name) # get the class attribute of the same name to see if it is a function
if isinstance(Aattr,types.FunctionType):
def hackyfunction(self,*args,**kwargs):
... # copy all previous values of A attributes, replace them with instance attributes
returnvalue = Aattr(*args, **kwargs)
... # change everything back
return returnvalue
method = types.MethodType(hackyfunction, self)
return method
# now it can't be a function, so just return normally. self.name will default to A.name if there is no instance attribute
return object.__getattribute__(self,name)
答案 3 :(得分:0)
谢谢大家的好主意,他们帮助我实现了自己想要的东西,然后我就没了。
我重命名了原始类,将其修复为可以正常工作,并使用相同的旧名称创建了它的实例。
所有导入文件的代码都使用相同的名称,没有任何修改。我以前真的应该想到这一点。认真地。
如果有人真的想要每个人的成果,这就是我使用所有想法所做的(经过一些测试)
import inspect
import re
def method_remove_static(m,cls_name):
source = inspect.getsourcelines(m)[0]
for i in range(len(source)):
if 'staticmethod' in source[i]:
del source[i]
break
for j in range(i,len(source)):
if ' def ' in source[i]:
source[i] = source[i].replace('(','(self,',1).lstrip()
break
return re.sub(r'(?<!\w)%s\.'%cls_name,'self.',''.join(source))
def class_remove_indentation(cls):
source = inspect.getsourcelines(cls)[0]
for i in range(len(source)):
if ' class ' in source[i]:
source[i] = source[i].lstrip()
break
return ''.join(source)
def regular_var(x):
return (not inspect.ismethod(x) and
not inspect.isbuiltin(x) and
not inspect.ismethoddescriptor(x) and
not inspect.isclass(x) and
not inspect.isfunction(x))
#MAIN FUNCTION
def class_remove_static(cls,init = None,context = {}):
class newCls:
if init is not None:
__init__ = init
#Inner classes
for name,incls in inspect.getmembers(cls,predicate=lambda x: inspect.isclass(x)):
if name == "__class__": continue
exec(class_remove_indentation(incls),context)
setattr(newCls,name,context[name])
__init__ = init
#static methods are listed as functions
for name,method in inspect.getmembers(cls,predicate=inspect.isfunction):
if name == "__init__": continue
exec(method_remove_static(method,cls.__name__),context)
setattr(newCls,name,context[name])
#Class variables defined at time of call (almost)
for name,var in inspect.getmembers(cls,predicate=regular_var):
if (name == '__doc__' or name == '__module__' or
name == '__weakref__' or name =='__dict__'): continue
setattr(newCls,name,var)
return newCls
这实际上重新编译了源代码。我确信正则表达式sub可以破坏一些用例,但是对我有用。如果您的类基于正在使用的模块中的变量,请提供locals()
作为上下文,以及一个可选的init
函数,该函数必须接受至少一个参数(俗称为self
)。
这是一个简单的搜索并替换为非常不受欢迎的exec
。如果确实需要,请自行决定是否使用。