【问题标题】:Override attribute get behaviour but only by external methods in Python覆盖属性获取行为,但只能通过 Python 中的外部方法
【发布时间】:2020-11-15 04:58:08
【问题描述】:

考虑以下代码:

class A():
    def __init__(self, thing):
        self.thing = thing
        
    def do_something(self):
        if self.thing > 0:
            print('thing is positive')
        else:
            print('thing is not positive')
            
def some_function(a):
    if a.thing > 0:
        print('this thing is positive')
    else:
        print('this thing is not positive')
        
class B(A):
    @property
    def thing(self):
        return 0
    
    @thing.setter
    def thing(self, val):
        pass

    # Purposely don't want to override A.do_something
    
a = A(5)
print(a.thing)   # 5
a.do_something() # thing is positive
some_function(a) # this thing is positive
isinstance(a, A) # True

b = B(5)
print(b.thing)   # 0
b.do_something() # thing is not positive  (!!! - not what we want - see below)
some_function(b) # this thing is not positive
isinstance(b, A) # True

假设do_something 是一个我们不想重写的复杂函数。这可能是因为它位于外部包中,我们希望能够继续使用包含A 的此包的最新版本,而不必每次都更新B。现在假设一个外部函数通过直接引用它来访问a.thing。我们希望B 扩展A 以便这个外部函数总是看到b.thing == 0。但是,我们希望在不修改内部方法行为的情况下做到这一点。在上面的例子中,我们想要修改some_function 的行为,但是我们这样做的代价是同时改变了内部方法b.do_something 的行为。

解决这个问题的明显方法是让外部函数some_function 使用get_thing() 方法。但是如果这些外部函数已经写在另一个包中,修改这些是不可能的。

另一种方法是让B 在调用父类的方法之前更新self.thing 的值,

class B(A):
    def __init__(self, thing):
        self.thing = 0
        self._thing = thing
    
    def do_something(self):
        self.thing = self._thing
        rval = super().do_something()
        self.thing = 0
        return rval

然而这看起来很笨拙,如果A 的开发者添加了新方法,那么B 会在这些方法没有更新的情况下改变这些方法的行为。

关于如何扩展这样的类是否有最佳实践,如果由外部函数调用,它允许使用覆盖__getattribute__,但不改变任何内部行为?

【问题讨论】:

    标签: python oop inheritance properties getter-setter


    【解决方案1】:

    我认为你可以像下面这样设置class B来实现你想要的:

    class B:
        def __init__(self, thing):
            self.thing = 0
            self._a = A(thing)
    
        def __getattr__(self, name):
            return getattr(self._a, name)
    

    完整代码如下:

    class A:
        def __init__(self, thing):
            self.thing = thing
    
        def do_something(self):
            if self.thing > 0:
                print('thing is positive')
            else:
                print('thing is not positive')
    
    
    def some_function(a):
        if a.thing > 0:
            print('this thing is positive')
        else:
            print('this thing is not positive')
    
    
    class B:
        def __init__(self, thing):
            self.thing = 0
            self._a = A(thing)
    
        def __getattr__(self, name):
            return getattr(self._a, name)
    
    
    if __name__ == '__main__':
        a = A(5)
        print(a.thing)    # 5
        a.do_something()  # thing is positive
        some_function(a)  # this thing is positive
    
        b = B(5)
        print(b.thing)    # 0
        b.do_something()  # thing is positive
        some_function(b)  # this thing is not positive
    

    【讨论】:

    • 谢谢 - 这似乎是一个很好的解决方法。我唯一的问题是它与扩展A 不完全相同,因为我不能做isinstance(b, A)。 [可能相关:stackoverflow.com/questions/62903828/…]。
    猜你喜欢
    • 2016-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-10
    • 2014-01-17
    • 2019-07-25
    • 2021-01-11
    • 1970-01-01
    相关资源
    最近更新 更多