Python:如何覆盖类的方法,同时保留装饰器并调用原始方法?

时间:2014-08-15 02:51:55

标签: python python-2.7 decorator descriptor

如何覆盖Class'方法,基于这种情况?但是,我无法编辑solid.py和run_me.py。虽然主打装饰,并能够调用原件。

# - solid.py - (no control)
import http

class Solid(object):
    _cp_path = '/pos'

    @http.jsonrequest
    def break_apart(self):
        return "to pieces!"

# - me.py -
import solid

def break_apart(self):
    return "to sand! and " + super(solid.Solid, self).break_apart()

solid.Solid.break_apart = break_apart

# - run_me.py - (no control)
import me    # yes, me first
import solid

pebble = solid.Solid()
pebble.break_apart() # "to sand! and to pieces!"

修改

感谢您的帮助,抱歉不完整。我忘了添加solid.py有装饰器。然而,monkeypatch的作用就像魅力一样:

  • 我失去了原来的装饰
  • 我不能称之为原始

    属性错误:'超级'对象没有属性' break_apart'

4 个答案:

答案 0 :(得分:1)

由于Python的功能是一流的,请将你的me.py设为:

import solid

# replacement method
def break_apart(self):
    return "to sand!"

# override Solid.break_apart with the new method
solid.Solid.break_apart = break_apart

另外,既然你调用了pebble.break_apart(),这意味着solid.py应该是:

class Solid(object):
    def break_apart(self):
        return "to pieces!"

注意在break_apart中添加了self参数。当你调用pebble.break_apart()(即self = pebble实例)

时,这是暗示的

答案 1 :(得分:1)

首先,你实际拥有什么样的课堂方法?

它看起来像staticmethod,但你没有使用staticmethod装饰器。

实例方法如下:

class C:
    def instance_method(self, *args, **kwargs):
        return self.foo

类方法如下:

    @classmethod
    def class_method(cls, *args, **kwargs):
        return cls.foo

静态方法如下:

    @staticmethod
    def static_method(*args, **kwargs):
        return foo

如果您无法更改run_me.py,则无法使用继承来更改此代码。

相反,你可以简单地monkeypatch Solid类,其兼容的break_apart实现如下:

import solid

def break_apart(self):
    return "whatever you want"

solid.Solid.break_apart = break_apart

当然,由于您的代码首先被导入,实际上您只能替换整个类。

所以只需定义Concrete,然后在solid模块中修补整个类:

import solid

class Concrete:
    ...

solid.Solid = Concrete

答案 2 :(得分:0)

我认为您可以采用两种不同的方法来覆盖您无法编辑源代码的类中的方法。

第一个是创建一个覆盖该方法的子类。这非常简单,但它只会影响您使用子类的构造函数创建的对象,而不是原始类:

me.py:

import http

import solid

class MySolid(solid.Solid):
    @http.jsonjequest
    def break_apart(self):
        return "to sand! and " + super(solid.Solid, self).break_apart()

我实际上并不知道http.jsonrequest装饰器的作用,因此如果它以某种方式修改了呼叫签名,您可能需要将调用更改为原始值才能使用它。

另一种方法是修补现有的类。这意味着您使用自己的替代版本替换类中的方法实现。如果原始类的实例是由您也无法控制的其他代码创建的,则此选项非常有用。请注意,如果您仍需要访问原始方法实现,则需要自行保存对它的引用(super无法处理此情况。)

me.py:

import http

import solid

_orig_break_apart = solid.Solid.break_apart

@http.jsonrequest
def break_apart(self):
    return "to sand! and " + _orig_break_apart(self)

solid.Solid.break_apart = break_apart

此处,如果装饰者更改了签名,您可能需要更改调用原始方法的方式。

该方法的装饰版本可能与未修改的版本大不相同(例如,它从根本上改变了调用签名或返回值)。如果是这种情况,可能并不容易“倒退”#34;这个过程来获取原始方法实现,以便您可以在新版本中调用它。在这种情况下,您可能需要将现有实现中的代码复制到覆盖版本(使用上述任一覆盖方法)。在示例代码中,您只需返回"to sand! and to pieces!"而不尝试调用原始方法。在实际代码中,这可能会更复杂,但同样的想法也适用。

答案 3 :(得分:0)

在stackoverflow上站在巨人的肩膀上,这对我有用。我知道这有点复杂,但是我还没有偶然发现更简单的东西。该代码适用于Python3。

dbg = $("div[class*='product_']") + type_id;

结果:

import functools
def log( fn ):
    @functools.wraps( fn )
    def wrapper( *args, **kwargs ):
        print( "log", fn.__name__, args[ 1: ] )
        return fn( *args, **kwargs )
    return wrapper

def prepare_class( clazz ):
    @classmethod
    def on_class_patcher( cls, func_name, context ):
        def patch_by_name( new_func) :
            original_func = getattr( cls, func_name )
            def patched_func( self, *args, **kwargs ):
                return new_func( self, original_func, context, *args, **kwargs )
            setattr( cls, func_name, patched_func )
        return patch_by_name

    setattr( clazz, "patch", on_class_patcher )

# --- Use like this ---
class Demo:
    @log
    def m1( self, x, y ):
        print( "Demo.m1" )
        print( x, y )

    def m2( self ):
        print( "Demo.m2" )

class Foo:
    def m3( self ):
        print( "Foo.m3" )

prepare_class( Demo )
foo = Foo()

@Demo.patch( "m1", context = { "foo": foo } )
def patched_m1( self, original_func, context, *args, **kwargs ):
    print( "Demo.m1_patched" )
    self.m2()
    context[ "foo" ].m3()
    x = args[ 0 ] * 2
    y = args[ 1 ] * 2
    return original_func( self, x, y )

demo = Demo()
demo.m1( 1, 2 )