【问题标题】:Prevent access to an instance variable from subclass, without affecting base class防止从子类访问实例变量,而不影响基类
【发布时间】:2016-01-26 21:33:43
【问题描述】:

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

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

我想创建一个子类Bar 并防止xBar 的实例更改,但仍使用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

所以基本上我已经围绕一个属性创建了一些包装函数 (do_stuff()),现在我想防止直接更改该属性,因为它可能会弄乱包装函数的某些功能。这是否可能以合理的方式进行?

编辑用一个更好的例子来说明我想要什么。我并不是要阻止他们看到变量 x,而是从 do_stuff() 之外更改它

【问题讨论】:

  • 所以你不想直接访问变量但想在函数中使用。我理解正确吗?
  • @udhy 是的,完全正确。我想阻止 其他人 在实例化 Bar 后访问该属性。因此,当有人使用bar = Bar(4); bar.x = 5 时,它会引发错误并告诉他们改用do_stuff()
  • Foo.__init__ 拥有对x 的所有其他访问权限。除非您明确检查 如何 x 被分配,例如与inspect,我不明白你怎么能做到这一点。我们都是同意的成年人,所以真的不支持像这样锁定东西。
  • @jonrsharpe 我想这是对我的“这可能以合理的方式吗?”的一个“否”。太糟糕了,虽然我真的不认为这是可能的。不过,值得一问!

标签: python class python-3.x inheritance


【解决方案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()

【讨论】:

  • 这是一个可行的解决方案,但原来的 Foo 类比我告诉你们的要复杂得多。它有一个自定义元类和它上面的长继承树。我不会走这条路(如果有的话),但这是一个可行的解决方案,如果我没有想出其他任何东西,我会记住它!谢谢你的想法:)
【解决方案2】:

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

在这里,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)

但无意中更难做到。

【讨论】:

  • 你的答案的第一部分是完全不相关的(好吧,至少对 me 没用),因为不幸的是,我无法更改原始基类 Foo 的命名方式变量。我已经回复了@NoctisSkytower 关于你提出的相同建议(不使用继承):“这是一个可行的解决方案,但原始的 Foo 类比我告诉你们的要复杂得多。它有一个自定义元类和一个它上面有很长的继承树。我不会走这条路(如果有的话),但这是一个可行的解决方案,如果我没有想出其他任何东西,我会记住它!“
【解决方案3】:

您走在正确的轨道上,您希望将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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-04-21
    • 1970-01-01
    • 2013-01-16
    • 1970-01-01
    • 1970-01-01
    • 2010-10-23
    • 2021-05-05
    相关资源
    最近更新 更多