使用所有“静态”方法从类创建“普通”类

时间:2019-01-04 14:38:29

标签: python python-3.x

从前我写了一个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的实例-请不要判断:)。要求:

  1. 我无法放弃Singleton版本。我使用了太多次,所以我不能只重写它。
  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

任何可以动态创建内容的解决方案(无论是否为hack)都可以。我目前正在考虑使用inspect构建它,尽管不确定它是否会成功。

4 个答案:

答案 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

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

  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

我不愿意尝试进一步自动化它;与手动更新每种方法相比,您可能会花费更多的时间来确定和尝试处理极端情况。

答案 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。如果确实需要,请自行决定是否使用。