【问题标题】:How to override an instance method at instance level in Python without replacing it?如何在 Python 中的实例级别覆盖实例方法而不替换它?
【发布时间】:2019-09-19 21:29:48
【问题描述】:

说明

我的问题与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()-method 已替换为

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)

这个解决方案基本上可以满足我的所有需求,只是它直接用新定义替换了提供的class1_obj 中的b()-方法。 因此,一旦调用了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 列表很烦人),如果我愿意,我现在可以在new_class1_obj 中使用原始class1_obj。 但是,这里的问题是new_class1_objbb() 方法将无法正常工作,因为它将使用class1_objb() 方法而不是new_class1_obj 的方法之一。 据我所知,如果不知道bb() 方法以这样的形式存在,就无法强制执行此操作。 由于可能有人继承了Class1 并引入了一个调用b()c() 方法,因此该解决方案将无法正常工作(而第一个解决方案可以正常工作)。

这里不继承Class1 会消除一些烦恼,但这也意味着isinstance 之类的函数不再正常工作,同时不能用bb() 方法解决问题。

解决方案?

所以,我目前无法为此提出解决方案(我已经尝试了几天)。 我正在考虑使用我的第一次尝试解决方案,但不是立即替换b()-方法,而是首先将b() 分配给_old_b()_b()(显然确保它不存在)然后替换b()。 我不是很喜欢那个解决方案,因为它仍然让我觉得太老套和肮脏。

那么,有人对此有想法吗? 在我看来,这听起来是一个非常简单的问题:我有一个实例,我想用一个新的实例方法更新它的一个实例方法,而不修改原始实例。 但是,这似乎并没有那么简单。

示例

一个完整的例子是:

# 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 new b()-method
def b(self):
    # Return both old and new b() output
    return(class1_obj.b(), "This is the new Class1 b()-method!")


# Define function that returns new Class1 object
def get_new_Class1_obj(class1_obj):
    <code involving getting new_class1_obj>
# Use with expected outputs
>>> class1_obj = Class1('TEST')
>>> new_class1_obj = get_new_Class1_obj(class1_obj)
>>> class1_obj is new_class1_obj
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 = 'TEST2'
>>> class1_obj.name
'TEST2'
>>> new_class1_obj.name
'TEST2'

【问题讨论】:

  • 在什么情况下/如何调用 class1_obj 的原始 b() 函数?也许这会给我们一个关于如何进行的想法
  • @CodelessBugging 好吧,例如,如果用户想同时使用class1_objnew_class1_obj。但是,我还要求可以从new_class1_obj 调用原始的b()-方法。编辑:我添加了一个例子。
  • 您的问题似乎源于 bb() 方法,无论是在原始实例还是新实例中,它仍然需要调用旧的 b() 方法,但新实例上的任何外部调用者都是重定向到新的b()。看来你应该直接在类中建模。
  • @quamrana 不,我希望new_class1_obj 中的bb() 方法使用新的b() 方法,而不是旧方法。只有new_class1_obj本身定义的方法才允许使用旧的b()-method。
  • 我有一个实例,我想用一个新的实例方法更新它的一个实例方法,而不修改原始实例。 存在矛盾。更新实例方法意味着修改实例。

标签: python methods overriding instance


【解决方案1】:

我不确定你想要什么,但下面的这个合适吗? (文件 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!')

这是你想要的吗?

我查看了我的答案,在我看来,这次与您上面的示例很接近。你怎么看?

【讨论】:

  • 它接近我想要的,但这需要我知道bb()-方法存在。由于我一直没有这方面的知识(如我的帖子所示),我很遗憾不能这样做。
  • 我查看了我的答案,在我看来,这一次接近你上面的例子。你怎么看?
  • 注意:如果您需要 deepcopy,上面@quamrana 的更新答案比我的要好
  • 感谢您的回答,但这需要一直重新初始化实例,这是我不想要的。新旧实例仍应相互链接。
【解决方案2】:

可能的解决方案

看了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。 如果我确实想要相反的工作(向class1_obj 添加新属性会影响new_class1_obj),我可以将确定props 添加到__getattribute__

【讨论】:

  • 是的,这是个好主意:你让你的新类遵循旧类的所有非方法,甚至是以后可能添加的新属性。
【解决方案3】:

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

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

您可以看到有趣数据的变化会影响这两个类。

【讨论】:

  • 也非常接近,但这不会使用class1_obj 拥有的所有属性。例如,尝试调用new_class1_obj.name 将不起作用,因为它只存在于class1_obj 中。我实际上会将它添加到示例中。
  • 有趣的想法,但这里的问题是,对我将在此处实际使用的实例 (mpi4py.MPI.Intracomm) 进行深层复制是不可能的。制作副本也意味着重新初始化所有内容,这意味着旧实例中属性的更改不会在新实例中更新。你明白我的处境吗? :)
  • 所以也许你只需要一个copy()
  • copy() 不起作用,因为我希望将两个实例链接在一起。 class1_obj 中的所有信息都应该可以在 new_class1_obj 中访问。这包括所做的更改。
  • 更新了将所有有趣的数据移动到不同对象的新想法。
猜你喜欢
  • 2010-09-28
  • 2012-07-19
  • 1970-01-01
  • 2019-12-16
  • 1970-01-01
  • 1970-01-01
  • 2018-01-07
  • 1970-01-01
  • 2012-02-27
相关资源
最近更新 更多