Python继承和__getattr__& __getattribute__

时间:2014-12-03 06:23:45

标签: python-2.7 inheritance getattr getattribute

我整天都在为此而奋斗并进行了大量的Google搜索。我有一个似乎是继承问题。

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

  1. 所有过去的继承都是单一的继承。换句话说,我继承了BaseClass,但继承BaseClass的类不会被另一个类继承。在今天的问题中,我将BaseClass继承到BaseFormData,然后继承到July2013FormData,然后继承到Jan2014FormData。显然现在有更多层。
  2. 我没有尝试在过去继承BaseClass的任何类中覆盖__getattr____getattribute__。今天我是。 BaseClass有自己的__getattribute__方法,以便管理Read Only属性的获取。 BaseFormData类有一个__getattr__方法,因为我读过这是提供数据访问权的正确方法,而不必显式声明所有属性。正在加载的表单数据中包含十几个或更多数据(取决于版本),因此我明确声明了一些重要或别名的属性,而其余属性由__getattr__处理。
  3. 以下是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__以使Read Only功能正常工作。

    来自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的_raw中删除了对_getattribute__的任何引用。我接受了rchang关于将self改为object的建议,这在BaseClass的__getattribute__中有所帮助,但似乎在BaseFormData中引起了问题。我从口译员处获得了“获取”部分,其代码如下:

    BaseClass的:

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

    BaseFormClass:

    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__问题。当我尝试覆盖Read Only attr时,日志警告会触发,但在调用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.
    

    -----------有它工作但看起来很笨拙...... -----------

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

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

2 个答案:

答案 0 :(得分:1)

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

也许尝试以下操作(只是使用你在try块中使用的object.__getattribute__扩展相同的技术到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]

答案 1 :(得分: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)

然后我需要对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个变化。首先是简单的,我删除了加注并让BaseClass处理它。更难描述变化的是我使用s.str_replace删除类名称的变更。如果没有这一行,Python通常会在_BaseClass__{some attr} dict中寻找类似_READ_ONLY的内容。但是,它仅在{some attr}字典中命名为_READ_ONLY。所以我将_BaseClass_部分剥离出来。

显然这是一个黑客攻击。我不确定实现某种形式的别名是否比剥离它更好。可能更明确?无论哪种方式,都可以通过Python的内置方法来保持属性在类之间的唯一性。

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