为什么我得到这个AttributeError?

时间:2013-05-24 17:47:38

标签: python python-3.x

我正在将我的KML库从python 2更改为3,我在这里遇到了麻烦。无论何时创建KML对象,例如k = KML(),我都会收到以下错误:AttributeError: 'KML' object has no attribute 'doc'并引用给定代码的最后一行。我不知道这里发生了什么,因为我已经明确定义了属性。这是代码。

import xml.etree.ElementTree as ET

class KML(ET.Element):
    def __init__(self):
        super().__init__("kml")
        self.doc = ET.Element("Doc")
        super().append(self.doc)     #error points here

非常感谢任何帮助。谢谢!

1 个答案:

答案 0 :(得分:3)

这里的根本问题是xml.etree.ElementTree.Element不是设计为子类的。

我不认为这是故意的,他们只是没想到有人将其子类化,并且没有想到它。在Python 3中,几乎所有用纯Python编写的东西都可以很好地进行子类化,但是C-API类是一个不同的故事。如果您查看xml.etree.ElementTree.Element,它实际上是_elementtree.Element,即implemented in C(来自2.x的cElementTree的简单端口)。

让我们采用精简的实现来查看问题:

import xml.etree.ElementTree as ET

class KML(ET.Element):
    def __init__(self, *args):
        super().__init__('kml')

k = KML()
k.doc = 'Doc'

尝试分配到AttributeError时,这会引发k.doc。为什么?那么,调用__setattr__,你和ET.Element都没有实现,而内置函数的默认实现也行不通,因为你和ET.Element都没有将自己设置为可变内置函数class,所以会引发AttributeError。与您使用ET.Element而不是子类或使用int进行此操作完全相同。

可是:

class KML(ET.Element):
    def __init__(self, *args):
        super().__init__('kml')
        self.doc = 'Doc'

k = KML()

现在没有例外......但它也没有设置属性,因为您可以在设置后立即尝试访问self.doc,或者在创建后立即访问k.doc。那是因为当属性创建异常位于__new____init__内时会被吞下,这会使问题更难调试。

那么,你怎么办呢?


一种可能性是自己实施__setattr__

对于所有非子类化友好的C-API类,情况都不是这样,但在这种情况下,您实际上有一个__dict__的正确object实现{{ 1}}和朋友们一起使用,你就是没有这种实现。

你可以对其进行monkeypatch,或者尝试设置正确的多重继承(但是__setattr__会出现问题,原因与原始问题类似)。

但我认为明确地写它会简单得多:

Element

另一种可能性是通过阻止C实现替换它来强制执行纯Python。虽然这看起来像是一个糟糕的黑客,但它会起作用:

def __setattr__(self, attr, value):
    self.__dict__[attr] = value
def __delattr__(self, attr):
    del self.__dict__[attr]

最后,您可以使用import _elementtree del _elementtree.Element import xml.etree.ElementTree as ET API的lxml实现,与stdlib相比,它具有许多其他优势。当然它也有一些缺点,首要的是你需要手动安装它(它取决于你可能还需要安装的C库ElementTree)。