代码之家  ›  专栏  ›  技术社区  ›  kabanus

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

  •  0
  • kabanus  · 技术社区  · 5 年前

    从前,我写了一个单身班。实现是一个只包含静态方法的类-我在上添加了一个异常 __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. 需要定义一个 __初始化__ 方法
    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 ,尽管我不确定会不会成功。

    4 回复  |  直到 5 年前
        1
  •  2
  •   chepner    5 年前

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

    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. 手动标识第一个参数(如果有的话)是否是的实例。如果不是,设置 self = A ,否则, self = args[0] .
    4. 根据你在第三步中的发现。

    例如, A.m1() print(A.x) a = A(); a.mi() 结果 print(a.x) . 同样地, A.m2(8) A.y = 8 a.m2(8) a.y = 8

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

        2
  •  2
  •   Serge Ballesta    5 年前

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

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

    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))
    ()
    
        3
  •  1
  •   quacodas    5 年前

    self.x 而不是 A.x

    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)
    
        4
  •  0
  •   kabanus    5 年前

    谢谢你们所有人的好主意,他们帮助我实现了我想要的,在我遇到一个无聊的时刻之前:

    我重命名了原始类,将其修复为正常工作,并用相同的旧名称创建了一个实例。

    导入文件的所有代码都使用与原样相同的名称,没有任何修改。我真的应该早就想到这一点。说真的。


    (有些测试)

    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 . 如果你真的需要的话,你可以自己决定。