阻止从子类访问实例变量,而不影响基类

时间:2015-10-27 12:55:14

标签: python class python-3.x inheritance

假设我有一个简单的类Foo,它来自外部库,因此我无法直接更改它:

class Foo(object):
    def __init__(self, x):
        self.x = x

我想创建一个子类Bar并阻止xBar的实例进行更改,但仍使用x中的Bar方法。

这是我尝试过的,它可能会启发基本的想法,但不幸的是它不起作用:

class Bar(Foo):

    @property
    def x(self):
        return super().x

    @x.setter
    def x(self, value):
        raise NotImplementedError('Do not change x directly, use "do_stuff()" instead')

    def do_stuff(self, value):
        if <something>:
            super().x = value

所以基本上我已经在属性周围创建了一些包装函数(do_stuff()),现在我想防止直接更改属性,因为它可能会破坏包装函数的某些功能。这可能是合理的吗?

已编辑,并提供了我想要的更好示例。我不是要阻止他们看到变量x,而是将其从do_stuff()

之外更改

3 个答案:

答案 0 :(得分:1)

如果您愿意完全避免继承,这应该更容易实现:

def main():
    bar = Bar(123)
    bar.fizz()
    bar.buzz()
    bar.fizz()
    bar.set_x(456)
    print('bar.x =', bar.x)
    try:
        bar.x = 123
    except AttributeError:
        print('bar.x cannot be set directly')
    else:
        raise AssertionError('an AttributeError should have been raised')
    bar.mutate_x(789)
    bar.fizz()
    bar.set_x(0)
    bar.fizz()
    bar.mutate_x(1)
    bar.fizz()
    bar.set_x('Hello World')
    bar.fizz()


class Foo:

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

    def fizz(self):
        print(self.x)

    def buzz(self):
        self.x = None


class Bar:

    def __init__(self, x):
        self.__foo = foo = Foo(x)
        self.__copy_methods(foo)

    def __copy_methods(self, obj):
        for name in dir(obj):
            if name.startswith('__') or name.endswith('__'):
                continue
            attr = getattr(obj, name)
            if callable(attr):
                setattr(self, name, attr)

    @property
    def x(self):
        return self.__foo.x

    def set_x(self, value):
        if isinstance(value, int) and value > 0:
            self.__foo.x = value

    mutate_x = set_x

if __name__ == '__main__':
    main()

答案 1 :(得分:1)

简短的回答是:不,这是不可能的合理方式。

Python的指导原则,使用来自style guide的措辞是我们都是负责任的用户。这意味着代码被信任不会做愚蠢的事情,人们通常应该避免在没有充分理由的情况下搞乱其他人的课程。

防止人们意外更改值的第一个也是最好的方法是使用单个下划线(_variable)来标记它。但是,这可能无法为您提供意外修改变量的保护。

保护的下一步是使用双下划线。引自PEP-8

  

为避免与子类发生名称冲突,请使用两个前导下划线来调用Python的名称修改规则。

     

Python使用类名来破坏这些名称:如果类Foo具有名为__a的属性,则Foo .__ a无法访问它。 (坚持不懈的用户仍然可以通过调用Foo._Foo__a获得访问权。)通常,双重前导下划线应该仅用于避免与设计为子类的类中的属性发生名称冲突。

重整会使意外覆盖值更加困难。

我强调最后一句,因为它很重要。使用这种机制来防止意外访问成员并不是真正应该为很多成员做的事情。

在您的具体情况下,我解决问题的方式是根本不是子类。考虑:

class Foo(object):
    def __init__(self, x):
        self.x = x

class Bar():
    def __init__(self, x):
        self._foo = Foo(x)

    @property
    def x(self):
        return self._foo.x

    def do_stuff(self, value):
        # Validate the value, and the wrapped object's state
        if valid:
            self._foo.x = value

当然这意味着Bar必须包装要包装的所有Foo方法。是的,有人还可以,

b = Bar(100)
b._foo.x = 127 # shame on them :)

b = Bar(100)
b._foo = EvilFoo(127)

但是无意中更难做到。

答案 2 :(得分:0)

您正走在正确的轨道上,您希望将x设为属性,而不是让它成为子类中的属性。你出错的地方是试图将x的原始数据存储在super上。你想要做的是利用父类可以透明地使用子类的新属性并且不需要知道它现在是属性而不是属性的事实。这样的事情对你有用:

class Foo(object):
    def __init__(self, x):
        self.x = x

class Bar(Foo):

    _protected_x = None

    @property
    def x(self):
        return self._protected_x

    @x.setter
    def x(self, value):
        if self._protected_x is None:
            self._protected_x = value
        else:
            raise ValueError("Use set_x to change x.")

    def set_x(self, value):
        self._protected_x = value

b = Bar(12)
print b.x
b.set_x(5)
print b.x