如何在Python实例级别覆盖实例方法而不替换它?

时间:2019-05-01 09:27:15

标签: python methods override instance

说明

我的问题与here提出的问题非常相似,只是有一个区别:我不想直接编辑或替换原始类实例中的任何内容。

因此,基本上,假设我们有一个定义为的类

class Class1(object):
    """
    This is Class1.

    """

    def __init__(self, name):
        self.name = name

    def a(self):
        "Method a"
        return("This is the Class1 a()-method!")

    def b(self):
        "Method b"
        return("This is the Class1 b()-method!")

    def bb(self):
        "Method bb"
        return(self.b())

现在我要做的是创建一个用户可以调用的函数,该函数为我提供Class1(或任何派生子类)的实例。 然后,我返回一个行为与提供的实例完全相同的对象,除了b()方法已替换为

def b(self):
    return('This is the new Class1 b()-method!')

与该对象有关的所有其他事物都必须与提供的实例中的事物完全相同,以便可以在旧对象可以使用的任何地方使用新对象。 基本上就好像Class1(或任何使用的子类)的定义已经开始使用b()的定义一样。

尝试

因此,我已经尝试了一些方法,但是每个方法都有至少一个我不喜欢的问题。 请注意,我没有做任何检查,以查看所提供的对象是否确实是Class1类的实例或任何派生的子类,因为它没有为我的描述添加任何内容。


我尝试使用给定的here解决方案,该解决方案为我提供了以下条件:

from types import MethodType


# Define handy decorator for overriding instance methods
def override_method(instance):
    def do_override(method):
        setattr(instance, method.__name__, MethodType(method, instance))

    return(do_override)


# Define function that returns new Class1 object
def get_new_Class1_obj(class1_obj):
    # Override b-method
    @override_method(class1_obj)
    def b(self):
        return('This is the new Class1 b()-method!')

    # Return it
    return(class1_obj)

该解决方案基本上可以完成我想要的所有事情,只是它用新定义直接替换了提供的b()中的class1_obj方法。 因此,一旦调用get_new_Class1_obj()函数,原始的class1_obj将无法再以其原始状态使用。 这是一个问题,对于我拥有的应用程序,实际上我要求原始的b()方法仍然可用(在本示例中,我只是简单地不使用这种方法使它保持简单)。


我尝试执行此操作的另一种方式涉及使用类工厂(我尝试了几种不同版本,下面可能是我最接近我想要的版本):

# Define function that returns new Class1 object
def get_new_Class1_obj(class1_obj):
    # Define list with all methods that are overridden
    override_attrs = ['__init__', '__getattribute__', '__dir__', 'b']

    # Make a subclass of the class used for class1_obj
    # This is required for functions like 'isinstance'
    class new_Class1(class1_obj.__class__, object):
        # Empty __init__ as no initialization is required
        def __init__(self):
            pass

        # Make sure only overridden attributes are used from this class
        def __getattribute__(self, name):
            if name in override_attrs:
                return(super().__getattribute__(name))
            else:
                return(getattr(class1_obj, name))

        # Use the __dir__ of class1_obj
        def __dir__(self):
            return(dir(class1_obj))

        # Override b-method
        def b(self):
            return("This is the new Class1 b()-method!")

    # Initialize new_Class1
    new_class1_obj = new_Class1()

    # Return it
    return(new_class1_obj)

这也非常接近我想要的(即使不断更新override_attrs列表也很烦人),现在我可以在class1_obj中使用原始的new_class1_obj如果我想要的话。 但是,这里的问题是bb()的{​​{1}}方法无法正常工作,因为它将使用new_class1_obj的{​​{1}}方法而不是其中之一b()。 据我所知,在不知道方法class1_obj以这种形式存在的情况下是无法执行此操作的。 由于某些人可能会继承new_class1_obj的子类并引入一种调用bb()的{​​{1}}方法,因此该解决方案将无法正常工作(而第一种解决方案将可以正常工作)。

这里不对Class1进行子类化可以消除一些麻烦,但这也意味着c()之类的函数无法正常工作,而无法解决b()的问题方法。

解决方案?

因此,我目前无法为此提出解决方案(我已经尝试了几天)。 我正在考虑使用我的第一个尝试解决方案,但是我没有立即替换Class1方法,而是将isinstance分配给类似bb()b()的东西(显然要确保它还不存在),然后替换b()。 我真的不喜欢这种解决方案,因为它对我来说仍然太笨拙和肮脏。

那么,有人对此有想法吗? 在我看来,这听起来像是一个非常简单的问题:我有一个实例,我想用一个新的实例方法来更新其实例方法之一,而无需修改原始实例。 但是,看来这毕竟不是那么简单。

示例

一个完整的示例用法是:

_old_b()
_b()

3 个答案:

答案 0 :(得分:0)

我不确定您想要什么,但是下面的内容适合吗? (文件test.py用python 3.7.3测试正常)

class Class1(object):
    """
    This is Class1.

    """

    def __init__(self, name):
        self.name = name

    def a(self):
        "Method a"
        return("This is the Class1 a()-method!")

    def b(self):
        "Method b"
        return("This is the Class1 b()-method!")

    def bb(self):
        "Method bb"
        return(self.b())

def get_new_Class1_obj(class1_obj):

    new_Class1_obj = Class1(class1_obj.name)

    def b():
        return(class1_obj.b(), "This is the new Class1 b()-method!")

    new_Class1_obj.b = b
    return new_Class1_obj

if __name__ == "__main__":
    class1_obj = Class1('TEST')
    new_class1_obj = get_new_Class1_obj(class1_obj)
    print("are insts same? ",class1_obj is new_class1_obj)
    print("class1_obj.name?",class1_obj.name)
    print("class1_obj.a():",class1_obj.a())
    print("class1_obj.b():",class1_obj.b())
    print("class1_obj.bb():",class1_obj.bb())
    print("new_class1_obj.name?",new_class1_obj.name)
    print("new_class1_obj.a():",new_class1_obj.a())
    print("new_class1_obj.b():",new_class1_obj.b())
    print("new_class1_obj.bb():",new_class1_obj.bb())

此代码将返回:

$ python test.py
are insts same?  False
class1_obj.name? TEST
class1_obj.a(): This is the Class1 a()-method!
class1_obj.b(): This is the Class1 b()-method!
class1_obj.bb(): This is the Class1 b()-method!
new_class1_obj.name? TEST
new_class1_obj.a(): This is the Class1 a()-method!
new_class1_obj.b(): ('This is the Class1 b()-method!', 'This is the new Class1 b()-method!')
new_class1_obj.bb(): ('This is the Class1 b()-method!', 'This is the new Class1 b()-method!')

这是您想要的吗?

我查看了我的回答,这次对我来说似乎与您上面的示例很接近。您如何看待?

答案 1 :(得分:0)

我不知道这是否是您想要的,但是下面的代码与您的示例具有相同的输出:

import copy

class ComplexObject:   # Equivalent of old Class1, but with no methods
    def __init__(self, name):
        self.name = name
        self.other_member = 'other, but could be intracomm'    

class Class1(object):
    def __init__(self, fwd):
        self.fwd = fwd  # All the interesting data is in this instance

    def get_name(self):
        return self.fwd.name  # Need to go through fwd to get at data
    def a(self):
        "Method a"
        return("This is the Class1 a()-method!")

    def b(self):
        "Method b"
        return("This is the Class1 b()-method!")

    def bb(self):
        "Method bb"
        return(self.b())

# Define function that returns new Class1 object
def get_new_Class1_obj(class1_obj):
    def b(self):
            # Return both old and new b() output
            return(class1_obj.b(), "This is the new Class1 b()-method!")
    new_instance = copy.copy(class1_obj)
    def b_wrapper():
        return b(new_instance)
    new_instance.b = b_wrapper

    return new_instance

complex=ComplexObject('TEST')
class1_obj = Class1(complex)
new_class1_obj = get_new_Class1_obj(class1_obj)
print("are insts same? ",class1_obj is new_class1_obj)
print("class1_obj.name",class1_obj.get_name())
print("class1_obj.a():",class1_obj.a())
print("class1_obj.b():",class1_obj.b())
print("class1_obj.bb():",class1_obj.bb())
print("new_class1_obj.name",new_class1_obj.get_name())
print("new_class1_obj.a():",new_class1_obj.a())
print("new_class1_obj.b():",new_class1_obj.b())
print("new_class1_obj.bb():",new_class1_obj.bb())
#Change some of the interesting data
class1_obj.fwd.name='FOO'
print("class1_obj.name",class1_obj.get_name())
print("new_class1_obj.name",new_class1_obj.get_name())

输出:

are insts same?  False
class1_obj.name TEST
class1_obj.a(): This is the Class1 a()-method!
class1_obj.b(): This is the Class1 b()-method!
class1_obj.bb(): This is the Class1 b()-method!
new_class1_obj.name TEST
new_class1_obj.a(): This is the Class1 a()-method!
new_class1_obj.b(): ('This is the Class1 b()-method!', 'This is the new Class1 b()-method!')
new_class1_obj.bb(): ('This is the Class1 b()-method!', 'This is the new Class1 b()-method!')
class1_obj.name FOO
new_class1_obj.name FOO

您可以看到有趣数据中的更改会影响两个类。

答案 2 :(得分:0)

可能的解决方案

在查看quamrana给出的答案之后,我想出了一个可能的解决方案,但是我希望对此有一些反馈:

from types import MethodType


# Define original Class1 class
class Class1(object):
    """
    This is Class1.

    """

    def __init__(self, name):
        self.name = name

    def a(self):
        "Method a"
        return("This is the Class1 a()-method!")

    def b(self):
        "Method b"
        return("This is the Class1 b()-method!")

    def bb(self):
        "Method bb"
        return(self.b())


# Define function that returns new Class1 object
def get_new_Class1_obj(class1_obj):
    # Obtain list of all properties that class1_obj has that are not methods
    props = [prop for prop in dir(class1_obj)
             if not isinstance(getattr(class1_obj, prop), MethodType)]

    # Make a subclass of the class used for class1_obj
    class new_Class1(class1_obj.__class__, object):
        # Empty __init__ as no initialization is required
        def __init__(self):
            pass

        # If requested attribute is not a method, use class1_obj for that
        def __getattribute__(self, name):
            if name in props:
                return(getattr(class1_obj, name))
            else:
                return(super().__getattribute__(name))

        # If requested attribute is not a method, use class1_obj for that
        def __setattr__(self, name, value):
            if name in props:
                setattr(class1_obj, name, value)
            else:
                super().__setattr__(name, value)

        # Use the __dir__ of class1_obj
        def __dir__(self):
            return(dir(class1_obj))

        # Define new b()-method
        def b(self):
            # Return both old and new b() output
            return(class1_obj.b(), "This is the new Class1 b()-method!")

    # Initialize new_Class1
    new_class1_obj = new_Class1()

    # Return it
    return(new_class1_obj)
# Do testing
if __name__ == '__main__':
    # Initialize instances
    class1_obj = Class1('TEST')
    new_class1_obj = get_new_Class1_obj(class1_obj)

    # Check that instances are not the same
    print(class1_obj is new_class1_obj)

    # Check outputs of class1_obj
    print(class1_obj.name)
    print(class1_obj.a())
    print(class1_obj.b())
    print(class1_obj.bb())

    # Check outputs of new_class1_obj
    print(new_class1_obj.name)
    print(new_class1_obj.a())
    print(new_class1_obj.b())
    print(new_class1_obj.bb())

    # Check that non-method changes in class1_obj affect new_class1_obj
    class1_obj.name = 'TEST2'
    print(class1_obj.name)
    print(new_class1_obj.name)

    # Check that non-method changes in new_class1_obj affect class1_obj
    new_class1_obj.name = 'TEST3'
    print(class1_obj.name)
    print(new_class1_obj.name)

这里的所有输出都是我想要的输出。 我仍然不确定是否希望new_class1_obj中的非方法更改影响class1_obj,但是我可以通过不覆盖__setattr__()来轻松地将其删除。 上面的内容还确保向new_class1_obj添加新属性不会影响class1_obj。 如果我确实想相反的话,可以向props添加确定__getattribute__(向class1_obj添加新属性会影响new_class1_obj)。