Python 继承和 __getattr__ & __getattribute__

Gab*_*lin 5 inheritance getattr python-2.7 getattribute

我一整天都在与这个问题作斗争,并进行了大量的谷歌搜索。我遇到了似乎是继承问题。

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

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

这是__getattribute__来自 BaseClass 的:

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]
Run Code Online (Sandbox Code Playgroud)

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

以及__getattr__来自 BaseFormData 的:

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
Run Code Online (Sandbox Code Playgroud)

这是我收到的错误:

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
Run Code Online (Sandbox Code Playgroud)

简而言之,我已经为此奋斗了一整天,因为通过这里或那里的调整,我得到了不同的反应。大多数时候__getattr__BaseFormData 从未被调用。其他时候我会遇到这个递归错误。其他时候我可以让它工作,但当我添加一些小东西时,一切都会再次崩溃。

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

我猜递归问题源于以下行__getattr__

tmp = super(BaseFormData, self).__getattribute__(name)
Run Code Online (Sandbox Code Playgroud)

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

任何帮助是极大的赞赏。

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

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

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

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

基类:

def __getattribute__(self, attr):
        try:
            # Default behaviour
            return object.__getattribute__(self, attr)
        except:
            return self.__READ_ONLY[attr]
Run Code Online (Sandbox Code Playgroud)

基本表单类:

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
Run Code Online (Sandbox Code Playgroud)

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

a = Jan2014Lead(leadFile)
Run Code Online (Sandbox Code Playgroud)

并获取实例 ID,如下所示:

a.__instanceId
Out[7]: '8c08dee80ef56b1234fc4822627febfc'
Run Code Online (Sandbox Code Playgroud)

这是实际的错误:

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.
Run Code Online (Sandbox Code Playgroud)

----------- 可以工作,但看起来很糟糕...... --------------

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

但这看起来像是一个黑客。有没有标准的方法来做到这一点?

Gab*_*lin 0

我不能说我已经完全满意地解决了这个问题。但是,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)
Run Code Online (Sandbox Code Playgroud)

然后我需要对 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
Run Code Online (Sandbox Code Playgroud)

这里实际上只有 2 处变化。首先是简单的,我删除了加注并让 BaseClass 处理它。更难描述的变化是我使用 去掉类名的变化s.str_replace_BaseClass__{some attr}如果没有这一行,Python 经常会在字典中寻找类似的内容_READ_ONLY{some attr}然而,它只是在字典中被命名_READ_ONLY。所以我把这_BaseClass_部分去掉。

显然这是一个黑客行为。我不确定实施某种形式的别名是否比删除它更好。可能更明确一点?无论哪种方式,绕过 Python 的内置方法来保持属性在类之间的唯一性似乎都是一种 hack。

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