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

Python 防止从子类访问实例变量,而不影响基类,python,class,python-3.x,inheritance,Python,Class,Python 3.x,Inheritance,假设我有一个简单的类Foo,它来自外部库,因此我不能直接更改它: class Foo(object): def __init__(self, x): self.x = x 我想创建一个子类Bar,防止x从Bar的实例中更改,但仍然使用Bar中的x方法 以下是我尝试过的,它可能会启发基本想法,但不幸的是,它不起作用: class Bar(Foo): @property def x(self): return super().x

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

class Foo(object):
    def __init__(self, x):
        self.x = x
我想创建一个子类
Bar
,防止
x
Bar
的实例中更改,但仍然使用
Bar
中的
x
方法

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

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
类栏(Foo):
@财产
def x(自我):
返回super().x
@x、 塞特
def x(自身,值):
raise NOTEImplementedError('不要直接更改x,改用“Do_stuff()”)
def do_材料(自身、价值):
如果:
super().x=值
因此,基本上我已经围绕一个属性创建了一些包装函数(
do_stuff()
),现在我想防止直接更改该属性,因为它可能会破坏包装函数的某些功能。这是否合理


用我想要的更好的例子编辑了。我并不是试图阻止他们看到变量
x
,而是从
do_stuff()

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

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()

简单的回答是:不,这是不可能的

Python在这里的指导原则是,我们都是负责任的用户。这意味着代码是可信的,不会做愚蠢的事情,人们通常应该避免在没有充分理由的情况下与其他人的类的成员发生冲突

防止人们意外更改值的第一个也是最好的方法是使用单个下划线(
\u变量
)对其进行标记。但是,这可能无法为您提供所需的保护,以防意外修改变量

保护的下一步是使用双下划线。引述自:

为了避免名称与子类冲突,请使用两个前导下划线来调用Python的名称混乱规则

Python将这些名称与类名相混淆:如果类Foo有一个名为_a的属性,那么Foo将无法访问它。(坚持的用户仍然可以通过调用Foo.\u Foo\u 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 :)


但是无意中这样做更难。

您走对了方向,希望将
x
作为属性,而不是将其作为子类中的属性。出错的地方是试图在super上存储
x
的原始数据。您要做的是利用这样一个事实:父类可以透明地使用子类的新属性,并且不需要知道它现在是属性而不是属性。像这样的东西应该适合你:

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

因此,您不希望直接访问变量,而是希望在函数中使用。我理解对了吗?@udhy是的,没错。我想阻止其他人在实例化
Bar
后访问该属性。所以当有人这样做时,
bar=bar(4);bar.x=5
它将引发一个错误,并告诉他们使用
do\u stuff()
来代替。
Foo.\uuu init\uuu
x的访问权限与其他操作相同。除非您明确检查
x
是如何分配的,例如使用
inspect
,否则我看不出您是如何分配的。我们都是同意的成年人,所以像这样把东西锁起来是不受支持的。@jonrsharpe我想这是对我的“这是否合理?”。太糟糕了,虽然我真的认为这是不可能的。不过,值得一问!检查这个-这是一个有效的解决方案,但是原始的
Foo
类比我告诉你们的要复杂得多。它有一个自定义元类,上面有一个长继承树。我还不会走这条路(如果有的话),但这是一个有效的解决方案,如果我没有其他想法,我会记住它!谢谢你的想法:)你答案的第一部分是非常不相关的(好吧,至少对我来说是无用的),因为,不幸的是,我无法更改原始基类
Foo
如何命名它的变量。我已经回复了@NoctisSkytower关于你提出的相同建议(不使用继承):“这是一个可行的解决方案,但是原始的Foo类比我告诉你们的要复杂得多。它上面有一个自定义元类和一个很长的继承树。我还不会走这条路(如果有的话),但这是一个有效的解决方案,如果我没有其他想法,我会记住它!”
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