【问题标题】:Python Inheritance and __getattr__ & __getattribute__Python 继承和 __getattr__ & __getattribute__
【发布时间】:2014-12-03 06:23:44
【问题描述】:

我整天都在为此奋斗,并进行了大量的 Google 搜索。我遇到了似乎是继承问题。

我有一个名为 BaseClass 的类,它为我做了一些简单的事情,比如设置日志默认值、保存日志和管理只读属性。过去我对这门课没有任何问题,但我可以说我现在只使用 python 几个月了。此外,我怀疑有几点很重要:

  1. 过去的所有继承都是单一继承。换句话说,我继承了 BaseClass 但继承 BaseClass 的类不会被另一个类继承。在今天的问题中,我将 BaseClass 继承到 BaseFormData 中,然后继承到 July2013FormData 中,然后继承到 Jan2014FormData 中。所以显然现在有更多的层。
  2. 我没有尝试在过去继承 BaseClass 的任何类中覆盖 __getattr____getattribute__。今天我是。 BaseClass 有一个自己的__getattribute__ 方法来管理只读属性的获取。 BaseFormData 类有一个__getattr__ 方法,因为我读到这是无需显式声明所有属性即可提供数据访问的正确方法。正在加载的表单数据中有十几条或更多条数据(取决于版本),因此我明确声明了一些至关重要或别名的属性,而其余的则由 __getattr__ 处理。

这是来自 BaseClass 的__getattribute__

def __getattribute__(self, attr):
        try:
            # Default behaviour
            return object.__getattribute__(self, attr)
        except:
            try:
                return self.__READ_ONLY[attr]
            except KeyError:
                lcAttr = php_basic_str.strtolower(attr)
                return self._raw[lcAttr]

使用self._raw 的行存在仅仅是因为继承BaseClass 的类经常使用_raw。 BaseClass 不需要_raw。理想情况下,对_raw 的引用只会出现在继承类的__getattr____getattribute__ 中。但是,我需要在 BaseClass 中使用 __getattribute__ 才能使只读功能正常工作。

还有来自 BaseFormData 的 __getattr__

def __getattr__(self, name):
        """
        Return a particular piece of data from the _raw dict
        """

        # All the columns are saved in lowercase
        lcName = s.strtolower(name)

        try:
            tmp = self._raw[lcName]
        except KeyError:
            try:
                tmp = super(BaseFormData, self).__getattribute__(name)
            except KeyError:
                msg = "'{0}' object has no attribute '{1}'. Note that attribute search is case insensitive."
                raise AttributeError(msg.format(type(self).__name__, name))

        if tmp == 'true':
            return True
        elif tmp == 'false':
            return False
        else:
            return tmp

这是我收到的错误:

File "D:\python\lib\mybasics\mybasics\cBaseClass.py", line 195, in __getattribute__
    return self.__READ_ONLY[attr]

RuntimeError: maximum recursion depth exceeded while calling a Python object

简短的版本是我整天都在为此奋斗,因为在这里或那里进行调整,我得到了不同的反应。大多数时候,来自 BaseFormData 的 __getattr__ 永远不会被调用。其他时候我得到这个递归错误。其他时候我可以让它工作,但后来我添加了一些小东西,一切又坏了。

关于继承和__getattribute____getattr__ 的调用顺序,我显然缺少一些东西。我今天对代码做了很多调整,如果没记错的话,我在 BaseFormData 和 BaseClass 中都不能有 __getattribute__,但我不记得我脑海中出现的错误。

我猜递归问题源于__getattr__ 中的这一行:

tmp = super(BaseFormData, self).__getattribute__(name)

显然,我想做的是首先查看当前类,然后转到 BaseClass __getattribute__ 以检查只读属性。

非常感谢任何帮助。

----------- 一些调整的结果.... -----------

所以我将 BaseFormClass __getattr__ 更改为 __getattribute__ 并且它似乎在 BaseClass 的 __getattribute__ 之前运行,这是有道理的。

但是它导致了无限递归,我认为这可能是由于 BaseFormClass 的 __init__ 和 BaseFormClass 的子类中发生的某些事情的顺序。然后似乎是由于 __READ_ONLY 创建得太晚了,我也修复了这个问题。

最终,_raw 在子类中而不是在 BaseClass 中,因此我删除了 BaseClass 的 _getattribute__ 中对 _raw 的任何引用。我接受了 rchang 关于将 self 更改为 object 的建议,这在 BaseClass 的__getattribute__ 中有所帮助,但似乎在 BaseFormData 中引起了问题。我已经使用以下代码从解释器中获得了“get”部分:

基类:

def __getattribute__(self, attr):
        try:
            # Default behaviour
            return object.__getattribute__(self, attr)
        except:
            return self.__READ_ONLY[attr]

BaseForm类:

def __getattribute__(self, name):
        # All the columns are saved in lowercase
        lcName = s.strtolower(name)

        try:
            # Default behaviour
            return object.__getattribute__(self, name)
        except:
            try:
                tmp = object.__getattribute__(self, '_raw')[lcName]
            except KeyError:
                try:
                    tmp = BaseClass.__getattribute__(self, name)
                except KeyError:
                    msg = "'{0}' object has no attribute '{1}'. Note that attribute search is case insensitive."
                    raise AttributeError(msg.format(type(self).__name__, name))

            if tmp == 'true':
                return True
            elif tmp == 'false':
                return False
            else:
                return tmp

具有讽刺意味的是,这又造成了另一个__getattribute__ 问题。当我尝试覆盖只读属性时,会触发日志警告,但随后在调用 self.__instanceId 时失败。这个日志警告昨天工作得很好(当我可以让类实例化而没有错误时)。我可以像这样实例化这个类:

a = Jan2014Lead(leadFile)

并像这样获取实例 ID:

a.__instanceId
Out[7]: '8c08dee80ef56b1234fc4822627febfc'

这是实际的错误:

File "D:\python\lib\mybasics\mybasics\cBaseClass.py", line 255, in write2log
    logStr = "[" + self.__instanceId + "]::[" + str(msgCode) + "]::" + msgMsg

  File "cBaseFormData.py", line 226, in __getattribute__
    raise AttributeError(msg.format(type(self).__name__, name))

AttributeError: 'Jan2014Lead' object has no attribute '_BaseClass__instanceId'. Note that attribute search is case insensitive.

----------- 得到它的工作,但似乎 hacky.... -----------

所以上面的错误是由于在 _READ_ONLY 中寻找 _BaseClass__instanceId 而 __instanceId 在 _READ_ONLY 中。所以我只是简单地修剪了传递的 attr 字符串以从头开始删除 _BaseClass。

不过,这似乎是一个 hack。有标准的方法吗?

【问题讨论】:

  • 我不清楚你的 __READ_ONLY 字典到底是干什么用的。由于您目前已经实现了__getattribute__,它只会在普通查找失败后进行检查,这意味着(除非您有一个您没有提到的__setattr__ 方法),可以分配常规实例变量并且它们' 将优先于字典的值。或许您应该不理会__getattribute__,而是编写一个__setattr__ 方法来为某些名称引发异常?
  • @Blckknght 我已经有一个__setattr__,当尝试覆盖只读属性时会引发异常。我只是认为这与这个问题无关。我在第一周左右编写了 BaseClass,当时我正在学习 python,__getattribute____setattr__ 的组合是我让它按预期工作的唯一方法。我可能需要重新访问它,但现在__setattr__ 不是问题。

标签: python-2.7 inheritance getattr getattribute


【解决方案1】:

我认为您遇到了无限递归,因为 __getattribute__ 试图直接查找 self 的属性,因此导致 __getattribute__ 在除了块之外的那些中调用自身。看到这个问题: How is the __getattribute__ method used?

也许尝试以下方法(只需使用 object.__getattribute__ 将您在 try 块中使用的相同技术扩展到 except 块)?

def __getattribute__(self, attr):
        try:
            # Default behaviour
            return object.__getattribute__(self, attr)
        except:
            try:
                # return self.__READ_ONLY[attr]
                return object.__getattribute__(self, __READ_ONLY)[attr]
            except KeyError:
                lcAttr = php_basic_str.strtolower(attr)
                # return self._raw[lcAttr]
                return object.__getattribute__(self, _raw)[lcAttr]

【讨论】:

  • 我尝试了一个简化版本,其中整个第二次尝试除了块之外只是成为返回 __READ_ONLY 行。仍然得到无限递归。在我看来,我真正的问题似乎是在 BaseFormData 的 __getattr____getattribute__ 之前调用 BaseClass 的 __getattribute__。我错过了一些东西......
【解决方案2】:

我不能说我完全明白这一点令我满意。但是,BaseClass __getattirbute__ 方法需要更改调用顺序:

def __getattribute__(self, attr):
        if attr in self.__READ_ONLY:
            return self.__READ_ONLY[attr]
        else:
            # Default behaviour
            return object.__getattribute__(self, attr)

然后我需要对 BaseFormClass 中的 __getattribute__ 进行另一项更改:

def __getattribute__(self, name):
        # All the columns are saved in lowercase
        lcName = s.strtolower(name)

        try:
            # Default behaviour
            return object.__getattribute__(self, name)
        except:
            name = s.str_replace('_' + type(self).__name__, '', name)
            lcName = s.strtolower(name)

            try:
                tmp = object.__getattribute__(self, '_raw')[lcName]
            except KeyError:
                #tmp = BaseClass.__getattribute__(self, name)
                tmp = super(BaseFormData, self).__getattribute__(name)

            if tmp == 'true':
                return True
            elif tmp == 'false':
                return False
            else:
                return tmp

这里实际上只有 2 处更改。首先是简单的,我删除了 raise 并让 BaseClass 处理它。更难描述的变化是我使用s.str_replace 去掉了类名。如果没有这条线,Python 经常会在 _READ_ONLY 字典中寻找类似 @​​987654326@ 的东西。但是,它仅在 _READ_ONLY 字典中命名为 {some attr}。所以我去掉了_BaseClass_ 部分。

显然这是一个 hack。我不确定实现某种形式的别名是否会比剥离它更好。可能更明确?无论哪种方式,绕过 Python 的内置方法来保持类之间的属性唯一性似乎都是一种技巧。

我想有更好的方法可以做到这一点,并且可能会在一年左右完全重写以反映这种更好的方式。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-01-04
    • 2011-05-16
    • 1970-01-01
    • 1970-01-01
    • 2017-12-12
    • 2011-06-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多