【问题标题】:python nested attributes encapsulationpython嵌套属性封装
【发布时间】:2015-10-17 10:45:50
【问题描述】:

我对 python 中的封装嵌套属性有一些疑问。让我们假设几个类: 这里我们有一个主类(DataWrapper),其中包括另外两个类:InnerWrapper1 和 InnerWrapper2。两个内部包装器都包含两个属性。

class DataWrapper(object):     
    @property
    def inner_wrapper1(self): 
        return self.__inner_wrapper1
    @inner_wrapper1.setter
    def inner_wrapper1(self, value): 
        self.__inner_wrapper1 = value

    @property
    def inner_wrapper2(self): 
        return self.__inner_wrapper2
    @inner_wrapper2.setter
    def inner_wrapper2(self, value): 
        self.__inner_wrapper2 = value

class InnerWrapper1(object):
    @property
    def property1(self): 
        return self.__property1
    @property1.setter
    def property1(self, value): 
        self.__property1 = value

    @property
    def property2(self): 
        return self.__property2
    @property2.setter
    def property2(self, value): 
        self.__property2 = value

class InnerWrapper2(object):
    @property
    def property3(self): 
        return self.__property3
    @property3.setter
    def property3(self, value): 
        self.__property3 = value

    @property
    def property4(self): 
        return self.__property4
    @property4.setter
    def property4(self, value): 
        self.__property4 = value

是否可以以某种方式覆盖 getattrsetattr 方法以实现以下封装?我想要实现的是从顶级类 DataWrapper 访问那些嵌套属性。

data_wrapper = DataWrapper()
data_wrapper.property1 = "abc"
...
var = data_wrapper.property2
...

我想到的第一件事是在 getattr 中执行 hasattr,但这给出了最大的递归深度...

这是一个完整的代码:

class DataWrapper(object):

    def __init__(self):
            self.inner_wrapper1 = InnerWrapper1()
            self.inner_wrapper2 = InnerWrapper2()

    @property
    def inner_wrapper1(self):
            return self.__inner_wrapper1
    @inner_wrapper1.setter
    def inner_wrapper1(self, value):
            self.__inner_wrapper1 = value

    @property
    def inner_wrapper2(self):
            return self.__inner_wrapper2
    @inner_wrapper2.setter
    def inner_wrapper2(self, value):
            self.__inner_wrapper2 = value

    def __setattr__(self, attribute, value):
            #if attribute in {'innerwrapper1', 'innerwrapper2'}:
            if attribute in ['inner_wrapper1', 'inner_wrapper2']:
                    return super(DataWrapper, self).__setattr__(attribute, value)
            if hasattr(self.inner_wrapper1, attribute):
                return setattr(self.inner_wrapper1, attribute, value)
            elif hasattr(self.inner_wrapper2, attribute):
                return setattr(self.inner_wrapper2, attribute, value)



    def __getattr__(self, attribute):
            try:
                    return getattr(self.inner_wrapper1, attribute)
            except AttributeError: pass

            try:
                    return getattr(self.inner_wrapper2, attribute)
            except AttributeError: pass

class InnerWrapper1(object):
    @property
    def property1(self):
            return self.__property1
    @property1.setter
    def property1(self, value):
            self.__property1 = value

    @property
    def property2(self):
            return self.__property2
    @property2.setter
    def property2(self, value):
            self.__property2 = value

class InnerWrapper2(object):
    @property
    def property3(self):
            return self.__property3
    @property3.setter
    def property3(self, value):
            self.__property3 = value

    @property
    def property4(self):
            return self.__property4
    @property4.setter
    def property4(self, value):
            self.__property4 = value

def main():
    data_wrapper = DataWrapper()
    data_wrapper.property1 = "abc"

if __name__ == "__main__":
    main()

【问题讨论】:

  • 答案是“是的,这是可能的”。你在实施它时有什么问题吗?如果是这样,你能告诉我们你尝试了什么吗?
  • 由于__getattr____setattr__ 只使用属性名称,您需要考虑如何将属性访问路由到一个或另一个内部对象。您是想硬编码属性(if name in ('property1', 'property2'): # use first inner objectelse: # ...)` 还是要动态测试属性?
  • 您可以edit您的问题添加您的尝试。如果这对您不起作用,会发生什么?
  • 嗨,Martijn。感谢您的快速回复。我已经更新了我的问题:) 好吧,但是“使用”到底是什么意思?因为当我在 __ getattr __ 中调用 getattr 时出现最大递归深度错误

标签: python properties attributes nested encapsulation


【解决方案1】:

您收到无限递归错误,因为您忘记在您的 __init__ 方法中设置 inner_wrapper1inner_wrapper2 属性。

当你这样做时:

self.inner_wrapper1 = InnerWrapper()

Python也会使用你的__setattr__ 方法。然后尝试使用尚不存在的self.inner_wrapper1,因此调用__getattr__,尝试使用尚不存在的self.inner_wrapper1,然后您进入无限递归循环。

__setattr__将属性设置为超类:

def __setattr__(self, attribute, value):
    if attribute in {'innerwrapper1', 'innerwrapper2'}:
        return super(DataWrapper, self).__setattr__(attribute, value)
    if hasattr(self.inner_wrapper1, attribute):
        return setattr(self.inner_wrapper1, attribute, value)
    elif hasattr(self.inner_wrapper2, attribute):
        return setattr(self.inner_wrapper2, attribute, value)

如果您对“私有”属性使用单个前导下划线(因此 _innerwrapper1_innerwrapper2),您可以进行测试:

def __setattr__(self, attribute, value):
    if attribute[0] == '_':  # private attribute
        return super(DataWrapper, self).__setattr__(attribute, value)

因此您不必对一整套名称进行硬编码。

由于您更新的完整脚本使用__inner_wrapper1__inner_wrapper2 作为实际的属性名称,并且您正在使用属性,因此您必须调整您的__setattr__ 测试以查找那些名字。因为您使用的是双下划线名称,所以您需要针对 name mangling of such attributes 进行调整:

def __setattr__(self, attribute, value):
    if attribute in {
            'inner_wrapper1', 'inner_wrapper2',
            '_DataWrapper__inner_wrapper1', '_DataWrapper__inner_wrapper2'}:
        return super(DataWrapper, self).__setattr__(attribute, value)

除非您要继承 DataWrapper 并且必须保护您的属性不被意外覆盖,否则我会完全避免使用双下划线名称。在 Pythonic 代码中,您不必担心其他代码访问属性,没有真正私有属性的概念。

在这里使用属性也是大材小用;属性不会给你买封装,在 Python 中你只会使用那些来简化 API(用属性访问替换方法调用)。

请注意,hasattr() 测试 InnerWrapper* property* 属性将失败,因为您没有默认值:

>>> inner = InnerWrapper1()
>>> hasattr(inner, 'property1')
False

hasattr() 不测试属性,它只是尝试访问一个属性,如果引发 any 异常,它会返回False

>>> inner = InnerWrapper1()
>>> hasattr(inner, 'property1')
False
>>> inner.property1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 43, in property1
AttributeError: 'InnerWrapper1' object has no attribute '_InnerWrapper1__property1'
>>> inner.property1 = 'foo'
>>> inner.property1
'foo'
>>> hasattr(inner, 'property1')
True

通过删除所有@property 对象,您可以大大简化这一过程:

class DataWrapper(object):
    def __init__(self):
        self._inner_wrapper1 = InnerWrapper1()
        self._inner_wrapper2 = InnerWrapper2()

    def __setattr__(self, attribute, value):
        if attribute[0] == '_':
            return super(DataWrapper, self).__setattr__(attribute, value)
        if hasattr(self._inner_wrapper1, attribute):
            return setattr(self._inner_wrapper1, attribute, value)
        elif hasattr(self._inner_wrapper2, attribute):
            return setattr(self._inner_wrapper2, attribute, value)

    def __getattr__(self, attribute):
        try:
            return getattr(self._inner_wrapper1, attribute)
        except AttributeError: pass
        return getattr(self._inner_wrapper2, attribute)

class InnerWrapper1(object):
    property1 = None
    property2 = None

class InnerWrapper2(object):
    property3 = None 
    property4 = None

【讨论】:

  • 再次感谢!唔。我仍然有无限递归......我已经更新了我的问题并在最后粘贴了整个脚本,以便更容易检查和执行。
  • @Konrad:你没有inner_wrapper1 属性。你有一个__inner_wrapper1 属性,所以测试一下。考虑到使用__ 双下划线会让你的生活变得更加困难,因为 Python 会将这些名称改造成_DataWrapper__inner_wrapper1
  • @Konrad:换句话说,您需要测试if attribute.startswith('_DataWrapper') 或使用if attribute in ['_DataWrapper__inner_wrapper1', '_DataWrapper__inner_wrapper2']
  • @Konrad:男孩,你确实让自己很难受。扩展了为什么您的代码不再工作。
  • 太棒了!非常感谢 Martijn!
猜你喜欢
  • 2011-02-23
  • 1970-01-01
  • 1970-01-01
  • 2021-11-17
  • 2011-10-03
  • 1970-01-01
  • 1970-01-01
  • 2014-07-26
  • 2013-01-06
相关资源
最近更新 更多