为什么会出现AttributeError错误?

3

我正在将我的KML库从Python 2转换为Python 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

非常感谢你的帮助。谢谢!


2
请附上完整的错误信息和整个堆栈跟踪。 - BrenBarn
1
你为什么要调用 super() 两次?你是不是想要执行 self.append(...) - shx2
1
@BrenBarn 你可以在解释器中运行它,它会返回一个没有真正堆栈跟踪的属性错误。在我的更长的代码中,k = KML()是从另一个文件中调用的,但我认为这并不重要。@shx2 我在代码中稍后定义了另一个append,我想运行ET.Element的append而不是KML的。 - mwillsey
问题在于您正在使用C实现的ElementTree(因为在3.2+中只要可以导入C实现,就会自动发生),这种方式不允许您以这种方式附加额外的属性。如果您尝试在__init____new__方法之外的任何地方使用k.doc = 'Doc',则将在该行上获得AttributeError。在__init____new__内部,它不会引发错误,但也不会执行任何操作,因此第一次尝试访问self.doc将引发错误。 - abarnert
无论如何,问题是您试图通过将成员附加到“Element”来实现什么。可能有一种更高级别的方法来完成相同的事情,或者您可能需要覆盖“__new__”,或者您可能只想切换到lxml的etree实现。 - abarnert
显示剩余2条评论
1个回答

3
这里的根本问题是 xml.etree.ElementTree.Element 没有被设计成可继承的。我认为这不是故意的,他们只是没有预料到会有人去继承它,并且也没有考虑过这个问题。在Python 3中,几乎所有用纯Python编写的东西都可以很好地进行子类化,但是C-API类就不同了。如果你看一下xml.etree.ElementTree.Element,它实际上是_elementtree.Element,而_elementtree.Element使用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'

当您尝试分配给“k.doc”时,它会立即引发“AttributeError”。为什么?这是因为调用了“__setattr__”,而您和“ET.Element”都没有实现它,内置函数的默认实现也无法运行,因为您和“ET.Element”都没有将自己设置为可变的内置类,所以它会引发“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)实现的__setattr__和相关函数使用它,只是您没有这个实现。
您可以进行monkeypatching,或者尝试设置适当的多重继承(但是Element由于与原始问题类似的原因而会遇到问题)。
但我认为最简单的方法是明确地编写它:
def __setattr__(self, attr, value):
    self.__dict__[attr] = value
def __delattr__(self, attr):
    del self.__dict__[attr]

另一种可能性是通过阻止C实现替换纯Python实现来强制使用纯Python实现。虽然这似乎是一个不好的hack,但它可以工作:

import _elementtree
del _elementtree.Element
import xml.etree.ElementTree as ET

最后,您可以使用lxml实现的ElementTree API,它比stdlib的实现有许多其他优点。当然,它也有一些缺点,其中主要的一个是您需要手动安装它(并且它依赖于C库libxml2,您可能还需要安装它)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接