Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/276.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 如何用原始类中的包装器调用替换每个函数调用?_Python_Python 3.x_Oop - Fatal编程技术网

Python 如何用原始类中的包装器调用替换每个函数调用?

Python 如何用原始类中的包装器调用替换每个函数调用?,python,python-3.x,oop,Python,Python 3.x,Oop,我有两个类,其中一个是另一个的包装器。原始类中的函数使用名为forward的方法,但我希望它在包装后使用包装类的forward方法,而不是原始方法。例如: class A: def __init__(self): self.a = 1 def forward(self, x): return self.a + x def main(self, x): return self.forward(x) + 100 clas

我有两个类,其中一个是另一个的包装器。原始类中的函数使用名为
forward
的方法,但我希望它在包装后使用包装类的
forward
方法,而不是原始方法。例如:

class A:
    def __init__(self):
        self.a = 1

    def forward(self, x):
        return self.a + x

    def main(self, x):
        return self.forward(x) + 100


class Wrapper:
    def __init__(self, A):
        self.module = A

    def forward(self, x):
        # Long convoluted code.
        # ..
        # ..

        return self.module.forward(x)


classA = A()
wrapperA = Wrapper(classA)

# Goal: Make classA.main(..) use the forward function from Wrapper instead.
因为包装器类有需要运行的长而复杂的代码,所以我希望所有来自
main
forward
调用都是从包装器类而不是从原始类调用
forward

在Python中有没有实现这一点的方法

我没有使用继承的原因:

  • 实例化类A是内存密集型的。如果我收到类A对象作为输入,我想修改它的核心行为。没有实例化另一个对象

  • classA
    在运行时可以是不同的对象类型

  • --


    我想到的另一种方法是在
    Wrapper
    中重新定义
    main
    。但是,问题是在没有硬编码的情况下为中定义的每个方法自动执行此操作。

    您可以从
    A
    继承
    Wrapper
    ,并使用
    super
    访问父类

    class A:
        def __init__(self, child):
            self.a = 1
            self.child = child
    
        def forward(self, x):
            return self.a + x
    
        def main(self, x):
            return self.child.forward(x) + 100
    
    class Wrapper(A):
        def __init__(self):
            super(Wrapper, self).__init__(self, self)
    
        def forward(x):
            return "whatever"
    
    wrapperA = Wrapper()
    
    但是如果您希望使用类
    A
    ,只需从
    包装器继承
    A
    。否则,我就不知道出了什么问题。请不要随意使用函数。创建一个您希望使用的类,另一个类充当父类,不要混合角色

    #...
    class A(Wrapper):
        def __init__(self):
            super(A, self).__init__(self)
    #...
    
    在Python中,“一切都是对象”。包括类、函数和方法 在对象上

    因此,我们可以接受任何类,循环该类中的所有函数并修改 他们需要的话

    根据实际代码,问题中的问题可能会得到更好的解决 根据包装器的依赖关系,使用装饰器或元类 (它需要访问哪些值)。我将不讨论元类,因为对元类的大多数需求也可以使用类装饰器实现,因为类装饰器不太容易出错

    正如您在一篇评论中提到的,您可能有几个不同的类需要包装,类装饰器解决方案可能是一个很好的候选者。这样就不会丢失包装类的继承树

    下面是一个示例,不使用这两种方法,但完全按照要求执行;)

    使用
    \uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
    使用类装饰器 有了类装饰器,就不需要重写
    \uuuuu new\uuuu
    ,如果没有100%正确地实现,可能会导致难以调试的问题

    但是,它有一个关键的区别:它“就地”修改了现有类,因此在某种程度上丢失了原始类。尽管在不太可能的情况下,您可以保留对它的引用

    然而,就地修改它也意味着您不需要用新的“包装器”类替换应用程序中的所有用法,从而使它更容易在现有代码库中实现,并消除了忘记在新实例上应用包装器的风险

    from functools import update_wrapper
    
    
    def _wrap(func):
        """
        Wraps *func* with additional code.
        """
        # we define a wrapper function. This will execute all additional code
        # before and after the "real" function.
        def wrapped(*args, **kwargs):
            print("before-call:", func, args, kwargs)
            output = func(*args, **kwargs)
            print("after-call:", func, args, kwargs, output)
            return output
        # Use "update_wrapper" to keep docstrings and other function metadata
        # intact
        update_wrapper(wrapped, func)
    
        # We can now return the wrapped function
        return wrapped
    
    
    def wrapper(cls):
        for funcname in dir(cls):
            # We skip anything starting with two underscores. They are most
            # likely magic methods that we don't want to wrap with the
            # additional code. The conditions what exactly we want to wrap, can
            # be adapted as needed.
            if funcname.startswith("__"):
                continue
    
            # We now need to get a reference to that attribute and check if
            # it's callable. If not it is a member variable or something else
            # and we can/should skip it.
            func = getattr(cls, funcname)
            if not callable(func):
                continue
    
            # Now we "wrap" the function with our additional code. This is done
            # in a separate function to keep __new__ somewhat clean
            wrapped = _wrap(func)
    
            # After wrapping the function we can attach that new function ont
            # our `Wrapper` instance
            setattr(cls, funcname, wrapped)
        return cls
    
    
    @wrapper
    class A:
        def __init__(self):
            self.a = 1
    
        def forward(self, x):
            """
            docstring (to demonstrate `update_wrapper`
            """
            print("original forward")
            return self.a + x
    
        def main(self, x):
            return self.forward(x) + 100
    
    
    @wrapper
    class Demo2:
    
        def foo(self):
            print("yoinks")
    
    
    classA = A()
    otherInstance = Demo2()
    print(classA.forward(10))
    print(otherInstance.foo())
    print("docstring is maintained: %r" % classA.forward.__doc__)
    
    使用函数装饰器 另一种选择是使用单独的函数包装器,它与最初的问题有很大的不同,但可能仍然很有见地

    代码仍然使用相同的包装器函数,但这里的函数/方法是单独注释的

    这可能会提供更大的灵活性,可以让某些方法保持“未包装”,但很容易导致包装代码的执行频率高于预期,如
    main()
    方法所示

    from functools import update_wrapper
    
    
    def wrap(func):
        """
        Wraps *func* with additional code.
        """
        # we define a wrapper function. This will execute all additional code
        # before and after the "real" function.
        def wrapped(*args, **kwargs):
            print("before-call:", func, args, kwargs)
            output = func(*args, **kwargs)
            print("after-call:", func, args, kwargs, output)
            return output
        # Use "update_wrapper" to keep docstrings and other function metadata
        # intact
        update_wrapper(wrapped, func)
    
        # We can now return the wrapped function
        return wrapped
    
    
    class A:
        def __init__(self):
            self.a = 1
    
        @wrap
        def forward(self, x):
            """
            docstring (to demonstrate `update_wrapper`
            """
            print("original forward")
            return self.a + x
    
        @wrap  # careful: will be wrapped twice!
        def main(self, x):
            return self.forward(x) + 100
    
        def foo(self):
            print("yoinks")
    
    
    classA = A()
    print(">>> forward")
    print(classA.forward(10))
    print("<<< forward")
    print(">>> main")
    print(classA.main(100))
    print("<<< main")
    print(">>> foo")
    print(classA.foo())
    print("<<< foo")
    
    从functools导入更新\u包装
    def wrap(函数):
    """
    用附加代码包装*func*。
    """
    #我们定义了一个包装函数。这将执行所有附加代码
    #在“实”函数之前和之后。
    def包装(*args,**kwargs):
    打印(“调用前:”,func,args,kwargs)
    输出=func(*args,**kwargs)
    打印(“调用后:”,func,args,kwargs,output)
    返回输出
    #使用“更新包装器”保存docstring和其他函数元数据
    #完好无损
    更新包装(包装,func)
    #现在我们可以返回包装函数
    退货包装
    A类:
    定义初始化(自):
    self.a=1
    @包裹
    def前进(自身,x):
    """
    docstring(用于演示“更新”包装器`
    """
    打印(“原件转发”)
    返回self.a+x
    @包裹#小心:会被包裹两次!
    def干管(自身,x):
    返回自我向前(x)+100
    def foo(self):
    打印(“yoinks”)
    classA=A()
    打印(“>>>转发”)
    打印(A类正向(10))
    打印(“>main”)
    打印(A类主(100))
    打印(“>foo”)
    打印(classA.foo())
    
    印刷品("为什么它必须是一个包装器,而不是一个继承的类?这会自动解决这个问题。这很有意义。我也在考虑这个问题,但似乎继承的类需要在运行时动态推断要继承的类,因为实际上classA可以是不同的类。有什么方法可以做到这一点吗?你能影响
    一个
    的实例化方式吗,或者你只是从某个地方得到一个实例吗?是的,我可以从某个地方得到一个实例,因为你可以覆盖
    \uu getattr\uuuuuuuuu()
    包装器中
    类,并转发对类
    a
    实例的调用和属性访问,该实例在创建时作为参数传递给它。对于
    \uuuu setattr\uuuuu
    ,同样的
    a
    实例可以由多个
    包装器
    (或
    包装器的种类)包装,所以您可能不需要创建很多。这不会跳过包装类中冗长而复杂的代码吗(见上文)?@oldselflearner1959。该代码与以前在包装器中的代码一样,放在子类中。现在,您可以在包装器上调用
    main
    ,并将复杂的代码运行。您可以将包装器的引用传递给其父类,并且当您
    from functools import update_wrapper
    
    
    def wrap(func):
        """
        Wraps *func* with additional code.
        """
        # we define a wrapper function. This will execute all additional code
        # before and after the "real" function.
        def wrapped(*args, **kwargs):
            print("before-call:", func, args, kwargs)
            output = func(*args, **kwargs)
            print("after-call:", func, args, kwargs, output)
            return output
        # Use "update_wrapper" to keep docstrings and other function metadata
        # intact
        update_wrapper(wrapped, func)
    
        # We can now return the wrapped function
        return wrapped
    
    
    class A:
        def __init__(self):
            self.a = 1
    
        @wrap
        def forward(self, x):
            """
            docstring (to demonstrate `update_wrapper`
            """
            print("original forward")
            return self.a + x
    
        @wrap  # careful: will be wrapped twice!
        def main(self, x):
            return self.forward(x) + 100
    
        def foo(self):
            print("yoinks")
    
    
    classA = A()
    print(">>> forward")
    print(classA.forward(10))
    print("<<< forward")
    print(">>> main")
    print(classA.main(100))
    print("<<< main")
    print(">>> foo")
    print(classA.foo())
    print("<<< foo")