从包含所有“static”方法的类创建“normal”类

2024-10-05 14:27:27 发布

您现在位置:Python中文网/ 问答频道 /正文

从前,我写了一个单身班。该实现是一个只包含静态方法的类-我在__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

快进几年,现在我需要创建单例实例-请不要判断:)。要求:

  1. 我不能放弃单一版本。我用了太多次,所以我不能重写它。你知道吗
  2. 新类应该访问一个对象变量,而原来的类应该访问一个对象变量
  3. 所有staticmethod装饰器都需要删除
  4. 所有方法都需要一个额外的self参数。你知道吗
  5. 需要定义__init__方法
  6. 我不想复制粘贴和修复粘贴-该类很长,并将受到未来的变化,应始终适用于这两个版本,坚持上述要求。这样新版本的构建应该是动态的,并且依赖于原来的版本(我已经把自己搞砸了一次,没有超前的思考)。你知道吗

解决方案可以在原来的类中实现,但我认为这可能是不可能的,因此一个函数获取原来的类并输出一个新的类可能是可行的:

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

任何动态创建内容的解决方案(不管是否有黑客攻击)都是好的。我目前正在考虑使用inspect构建这个,尽管我不确定它是否会成功。你知道吗


Tags: 对象方法self版本initdefexception解决方案
3条回答

很难将所有副作用只能影响类本身的静态单例类转换为副作用只能影响实例对象的普通类。但也可以做相反的事情:只需使静态类拥有普通类的唯一实例并将其所有方法调用和属性访问委托给它,就可以将普通类转换为单例静态类。你知道吗

元类可以完成这项工作。这个方法创建一个特殊的属性_own来保存它的模型类的实例,显式地为它创建带有适当签名的方法(它还保留docstring,如果有的话),只是将调用委托给_own,还将所有属性访问委托给_own

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)

在您的示例中,非单例类将是b:

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

你可以简单地使用它:

>>> B.m1()
3
>>> B.m2(6)
>>> B.x
3
>>> B.y
6
>>> import inspect
>>> print(inspect.signature(B.m2))
(n)
>>> print(inspect.signature(B.m1))
()

对我来说,最大的问题似乎是让方法调用self.x而不是A.x这将是一个愚蠢的想法,但你说黑客修复是可以的,所以我们可以备份类属性的所有值,更改它们以匹配实例属性,然后调用staticmethod,然后还原所有值吗?如果你允许的话,这样的方法可能会奏效:

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)

我不确定这是否足够,但我认为下面的修改既可以用作原始单例,也可以用作普通类。有相当多的样板文件,但它是与类隔离的,而不是使用该类的代码。你知道吗

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

基本上,您将对每个方法执行以下操作:

  1. 去掉staticmethod装饰器
  2. *args替换每个方法的参数列表
  3. 手动确定第一个参数(如果有)是否是A的实例。如果不是,则设置self = A,否则设置self = args[0]。你知道吗
  4. 根据步骤3中的内容,使用*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。你知道吗

我会犹豫是否尝试进一步自动化;与手动更新每个方法相比,您可能会花费更多的时间来识别和解决棘手的问题。你知道吗

相关问题 更多 >