我正在将我的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
非常感谢任何帮助。谢谢!
答案 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
)。