python后端开发教程_Python后端开发

Python (78) 2023-03-24 16:06

本节是第五讲的第十五小节下,本节主要介绍文本文件和XML文件的生成以及处理等。

文本文件的写入与分析(Writing and Parsing Text Files)

写入文本是容易的,读回时则可能存在不少问题,因此,我们需要认真选择合适的结构,以便对其进行分析不至于太难。如下所示我们将要使用的文本格式展示了一个航空器事故实例。将航空器事故记录写入文件时,我们将在每个记录后附加一个空白行,但对文件进行分析时,我们可以接受在事故记录之间存在0个或多个空白行。

#航空器事故记录的实例文本格式

[20070927022009C]

date=2007-09-27

aircraft_id=1675B

aircraft_type=DHC-2-MKl

airport=MERLE K (MUDHOLE) SMITH

pilot_percent_hours_on_type=46.1538461538

pilot_total_hours=13000

midair=0

.NARRATIVE_START.

ACCORDING TO THE PILOT, THE DRAG LINK FAILED DUE TO AN OVERSIZED TAIL WHEEL

TIRE LANDING ON HARD SURFACE.

.NARRATIVE_END.

写入文本(Writing Text)

每条事故记录都以包含在方括号中的报告ID开始,其后跟随的是所有占据一行的数据项,其格式为key=value,对占据多行的叙述性文本,在文本前以开始标记 (NARRATIVE_START)引导,在文本末尾则以结束标记(NARRATIVE_END)结尾, 我们对两个标记之间的所有文本进行了缩排,以防止文本行与开始或结束标记混淆。

下面给出的是export_text()函数的代码,但没有给出except语句块与finally语句块,因为这两个语句块与前面相应实例中是相同的,不同之处仅在于待处理的异常:

叙述性文本中存在断行并不会有很大影响,因此我们可以按我们的需求对文本进行包裹。通常,我们可以使用textwrap模块的textwrap.wrap()函数,但这里我们同时需要缩排与包裹,因此我们首先创建了一个textwrap.TextWrap对象,并用我们将要使用的缩排(第一行与后续行都是4个空格)进行初始化。默认情况下,该对象可以包裹宽度为70个字符的行,当然,通过使用关键字参数,也可以对其进行改变。

def export_text(self, filename):

wrapper = textwrap.TextWrapper(initial_indent=” “,subsequent_indent=” “)

fh = None

try:

fh = open(filename, “w”, encoding=”utf8”)

for incident in self.values():

narrative = "\n".join(wrapper.wrap(incident.narrative.strip()))

fh.write("[{0.report_id}]\n"

"date={0.date!s}\n"

"aircraft_id={0.aircraft_id}\n"

"aircraft_type={0.aircraft_type}\n"

"airport={airport}\n"

"pilot_percent_hours_on_type="

"{0.pilot_percent_hours_on_type}\n"

"pilot_total_hours={0.pilot_total_hours}\n"

"midair={0.midair:d}\n"

".NARRATIVE_START.\n{narrative}\n"

".NARRATIVE_END.\n\n“.format(incident,

airport=incident.airport.strip(), narrative=narrative))

return True

我们可以使用三引号包含的字符串,但我们更愿意手动加入换行。textwrap.TextWrap 对象提供了一个wrap()方法,该方法以一个字符串作为输入,在这里就是叙述性的文本, 并返回一个字符串列表,带有适当的缩排,并且其中每个字符串的长度不超过wrap宽度。 之后,我们将这些行添加到一个单独的字符串中,并使用换行作为分隔符。事故日期以 datetime.date对象的形式存放,在写入日期数据时,我们强制str.format()使用字符串表示形式——这样可以很便利地产生符合ISO 8601的日期数据格式,也即YYYY-MM-DD格 式。此外,我们告知str.format()将布尔型变量midair写为整数——如果为True,就变为1, 如果为felse,就变为0。通常,str.format()可以使写入文本变得很容易,因为该方法可以 自动处理所有Python数据类型。(也包括自定义数据类型,如果实现了__str__()或 __format__()特殊方法。)

分析文本(Parsing Text)

与进行文本写入的方法相比,用于读取与分析文本格式航空器事故记录的方法要更长一些,也更棘手。读取文件时,我们可能会处于几种状态之间的一种:读取叙述性文本行的过程中;读取key=value行的过程中;读取报告ID行的过程中(在新事故记录的起始处)。我们将分5个部分来査看import_text_manual()方法。

def import_text_manual(self, filename):

fh = None

try:

fh = open(filename, encoding="utf8")

self.clear()

data = {}

narrative = None

该方法首先以“文本读”模式打开文件,之后清空事故字典,创建data字典,以便存放单个事故记录的数据(与二进制读事故记录时的做法相同)。narrative变量用于两个目的:作为一个状态指示器;存储当前事故记录的叙述性文本。如果narrative为 None,就意味着我们当前读取的不是叙述性文本;如果是一个字符串(即便是空字符串),也意味着我们正在读取叙述性文本行。

for lino, line in enumerate(fh, start=1):

line = line.rstrip()

if not line and narrative is None:

continue

if narrative is not None:

if line == ".NARRATIVE_END.":

data["narrative"] = textwrap.dedent(narrative).strip()

if len(data) != 9:

raise IncidentError("missing data on line {0}".format(lino))

incident =Incident(**data)

self[incident.report_id] = incident

data = {}

narrative = None

else:

narrative += line + "\n"

在釆用文本读模式时,数据的读入是逐行进行的,因此我们可以保持对当前行号的追踪,并提供包含信息更多的错误消息(与读取二进制文件相比)。我们首先剥离每行的结尾处的空白字符,如果处理之后剩下的是空行(假定没有处在读取叙述性文本的过程中),就简单地跳到下一行。这意味着,事故记录中的空白行并不会有什么影响, 我们保留叙述性文本中任意的空白行。如果narrative不为None,就说明我们处于读取叙述性文本的过程中。如果当前行表示的是叙述性文本的结尾标记,就说明我们不仅完成了叙述性文本的读取,也完成了整个事故记录的读取。在这种情况下,我们将叙述性文本存放到data字典(之前使用textwrap.dedent()函数移除缩排),假定我们获取了事故记录的9个要素,则创建一 个新事故,并将其存放在字典中。之后,清空data字典,并重置narrative变量,以备读取下一条记录。另一方面,如果当前行不包含叙述性文本标记,我们就将其附加到narrative中并剥离开始处的换行。

elif (not data and line[0] =="[" and line[-1] =="]"):

data["report_id"] = line[1:-1]

narrative为None,说明我们或者在读取新报告ID,或者在读取其他数据。只有在data字典为空(因为该字典最初为空,并且在读取每个事故记录结束后也将其清空) 并且该行以[开始并以]结束时,才可以判断当前在读取新报告ID。如果是这种情况,就将报告ID放置到data字典中。这也意味着,直至data字典下一次被清空,elif条件才会变为True。

elif "=" in line:

key, value = line.split("=”, 1)

if key=="date”:

data[key] = datetime.datetime.strptime(value,"%Y-%m-%d').date()

elif key == "pilot_percent_hours_on_type":

data[key] = float(value)

elif key = "pilot_total_hours":

data[key] = int(value)

elif key = "midair":

data[key] = bool(int(value))

else:

data[key] = value

elif line ==".NARRATIVE_START.":

narrative=""

else:

raise KeyError("parsing error on line {0}".format(lino))

如果当前没有处于读取叙述性文本的状态或读取新报告ID的状态,就只有3种可能:当前正在读取key=value行;当前正在读取叙述性文本开始标记;出错。

如果当前正在读取key=value行,就可以使用第一个=字符分割该行,并指定一次分割的最大值这意味着value中可以安全地包含字符=。读入的所有数据都以Unicode字符串形式存在,因此,对于日期、数值型、布尔型等数据类型,我们必须相应地对值字符串进行转换。

对日期数据,我们使用datetime.datetime.strptime()函数(“字符串分析时间”),该函数以一个格式化字符串为参数,并返回一个datetime.datetime对象。我们使用了匹配ISO 8601日期格式的格式化字符串,并使用datetime.datetime.date()从产生的 datetime.datetime对象中取回一个datetime.date对象,因为我们需要的只是日期,而不是日期/时间。对数值型值得转换,我们依赖于Python内置的类型函数float()与int()。 尽管如此,要注意的是,int("4.0")这种语句会产生ValueError异常,如果接受整数时希望更具字面上的意义,可以使用int(float("4.0"));如果需要四舍五入,而不是截取, 则可以使用round(float("4.0"))。获取bool更加微妙,比如,bool("0")返回True (非空字符串都为True),因此,我们必须首先将字符串转换为int。无效的、丢失的、超出范围的值总是会产生异常。任意其他转换操作失败,都将产生ValueError异常。在数据用于创建相应的Incident对象时,任何值超出了范围, 都会产生IncidentError异常。

return True

except (EnvironmentError, ValueError, KeyError,IncidentError) as err:

print("{0}: import error: {1}".format(

os.path.basename(sys.argv[0]), err))

return False

finally:

if fh is not None:

fh.close()

某行不包含字符=,我们可以检查是否已经读取了叙述性文本开始标记,如果已经读取,就将narrative变量设置为空字符串,这意味着对后继行而言,第一个if条件为True—直至读取到描述性文本结束标记。

if条件或elif条件都不为True,说明有错误产生,因此,在最后的else语句中,产生一个KeyError异常来表示这一情况。读取所有行后,为调用者返回True——除非发生异常,在这种情况下,except语句块将捕获该异常,为用户打印出错误消息,并返回False。最后,不论哪种情况,打开的文件都要关闭。

写入与分析XML文件(Writing and Parsing XML Files)

有些程序将其处理的所有数据都使用XML文件格式,还有些其他程序将XML用作一种便利的导入/导出格式。即便程序的主要格式是文本格式或二进制格式,导入与导出XML的能力也是有用的,并且始终是值得考虑的一项功能。

Python提供了 3种写入XML文件的方法:手动写入XML;创建元素树并使用其 write()方法;创建D0M并使用其write()方法。XML文件的读入与分析则有4种方法: 人工读入并分析XML (不建议釆用这种方法);使用元素树;D0M (文档对象模型);SAX(Simple API for XML,用于 XML 的简单 API)分析器。

元素树(Element Trees)

使用元素树写入XML数据分为两个阶段:首先,要创建用于表示XML数据的元素树;之后,将元素树写入到文件中。有些程序可能使用元素树作为其数据结构,这种情况下,第一阶段可以省略,只需要直接写入数据。我们分两个部分来查看 export_xml_etree()方法:

def export_xml_etree(self, filename):

root = xml.etree.ElementTree.Element("incidents")

for incident in self.values():

element = xml.etree.ElementTree.Element("incident",

report_id=incident.report_id,

date=incident.date.isoformat(),

aircraft_id=incident.aircraft_id,

aircraft_type=incldent.aircraft_type,

pilot_percent_hours_on_type=str(

incident.pilot_percent_hours_on_type),

pilot_total_hours=str(inddent.pilot_total_hours),

midair=str(int(incident.midair)))

airport = xml.etree.ElementTree.SubElement(element,"airport")

airport.text = incident.airport.strip()

narrative = xml.etree.ElementTree.SubElement(element,"narrative")

narrative.text = incident.narrative.strip()

root.append(element)

tree = xml.etree.ElementTree.ElementTree(root)

<?xml version="1.0" encoding="UTF-8"?>

<incidents>

<incident report_id=”20070222W8099G” date="2007-02-22"

aircraft_id="80342" aircraft_type="CE-172-M"

pilotpercent_hours_on_type="9.09090909091"

pilot_total_hours="440" midair="0">

<airport>BOWERMAN</airport>

<narrative>

ON A GO-AROUND FROM A NIGHT CROSSWIND LANDING ATTEMPT THE AIRCRAFT HIT A RUNWAY EDGE LIGHT DAMAGING ONE PROPELLER.

</narrative>

</incident>

<incident>

...

</incident>

:

</incidents>

我们从创建根元素(<incidents>)开始,之后对所有事故记录进行迭代。对每条事故记录,我们创建一个元素(<incident>)来存放该事故记录的数据,并使用关键字参数来提供属性。所有属性必须都是文本,因此,我们需要对日期、数值型数据、布尔型数据项进行相应转换。我们不必担心对“&”、"<"、">”(或属性值中的引号)的转义处理,因为元素树模块(以及SOM、SAX模块)会对相关的详细资料进行自动处理。

每个<incident>包含两个子元素,一个用于存放机场名,另一个用于存放叙述性文本。创建子元素时,必须为其提供父元素与标签名。元素的读/写text属性则用于存放其文本。

<incident>及其所有属性、子元素<airport>与<narrative>创建之后,我们将其添加到树体系的根(<incidents>)元素,反复进行这一过程,最终的元素体系中就包含了所有事故记录数据,这些数据可以转换为元素树。

try:

tree.write(filename, "UTF-8")

except EnvironmentError as err:

print("{0}: import error: {1}".format(

os.path.basename(sys.argv[0]),err))

return False

return True

写入XML数据来表示一个完整的元素树,实际上只是使用给定的编码格式将元素树本身写入到文件中。

到现在为止,在指定编码格式时,我们几乎总是使用字符串"utf8",这对Python内置的open()函数而言是可以正常工作的,该函数可以接受很多种编码方式以及这些编码方式名称的变种,比如“UTF-8”、“UTF8”、“utf-8”以及“utf8”。但对XML文件而言,编码方式名称只能是正式名称,因此,“utf8”是不能接受的,这也是为什么我们严格地使用“UTF-8”。

使用元素树读取XML文件并不比写入难多少,也分为两个阶段:首先读入并分析XML文件,之后对生成的元素树进行遍历,以便读取数据来生成incidents字典。 同样地,如果元素树本身已经是内存中存储的数据结构,第二阶段就不是必要的。下面分两部分给出import_xml_etree()方法。

def import_xml_etree(self, filename):

try:

tree = xml.etree.ElementTree.parse(filename)

except (EnvironmentError,xmLparsers.expat.ExpatError) as err:

print("{0}: import error: {1}".format(

os.path.basename(sys.argv[0]), err))

return False

for element in tree.findall("incident"):

try:

data = {}

for attribute in ("report_id,"date", "aircraft_id","aircraft_type",

"pilot_percent_hours_on_type","pilot_total_hours", "midair"):

data[attribute] = element.get(attribute)

data["date"] = datetime.datetime.strptime(data[”date”],"%Y-%m-%d").date()

data["pilot_percent_hours_on_type"]=(float(data["pilot_percent_hours_on_type"]))

data["pilot_total_hours"] = int(data["pilot_total_hours"])

data["midair"] = bool(int(data["midair"]))

data["airport"] = element.find("airport").text.strip()

narrative = element.find("narrative").text

data["narrative"] = (narrative.strip()

if narrative is not None else "")

incident = lncident(**data)

self[incident.report_id] = incident

except (ValueError, LookupError, IncidentError) as err:

print("{0}: import error: {1}".format(

os.path.basename(sys.argv[0]), err))

return False

return True

准备好元素树之后,就可以使用xml.etree.ElementTree.fmdall()方法对每个行迭代处理了。每个事故都是以一个xml.etree.Element对象的形式返回的。 在处理元素属性时,我们使用的是与前面import_text_regex()方法中同样的技术。我们首先将所有值存储到data字典中,之后将日期、数字、布尔型值转换到正确的类型。 对机场属性与叙述性文本元素,我们使用xml.etree.Element.find()方法寻找这些值,并读取其text属性。如果某个文本元素不包含文本,那么其text属性将为None,因此, 在读取叙述性文本元素时,我们必须考虑这一点,因为该元素可以为空。在所有情况 下,返回给我们的属性值与文本都不包含XML转义,因为其是自动非转义的。

与用于处理航空器事故数据的所有XML分析器类似,如果航空器或叙述性文本元素丢失,或某个属性丢失,或某个转换过程失败,或任意的数值型数据超出了取值范围,都会产生异常——这将确保无效数据被终止分析并输出错误消息。用于创建并存储事故记录以及处理异常的代码与前面看到的相同。

DOM(Document Object Model)

DOM是一种用于表示与操纵内存中XML文档的标准API。用于创建DOM并将其写入到文件的代码,以及使用DOM对XML文件进行分析的代码,在结构上与元素树代码非常相似,只是稍长一些。我们首先分两个部分查看export_xml_dom()方法。这一方法分为两个阶段:首先创建一个DOM来表示事故记录数据,之后将该DOM写入到文件。就像使用元素树写入时一样,有些程序可能使用DOM作为其数据结构,在这种情况下可以省略第一 步,直接写入数据。

def export_xml_dom(self, filename):

dom = xml.dom.minidom.getDOMImplementation()

tree = dom.createDocument(None, "incidents", None)

root = tree.documentElement

for incident in self.values():

element = tree.createElement(incident)

for attribute, value in (("report_id", incident.report_id),("date", incident.date.isoformat()),("aircraft_id", incident.aircraft_id),("aircraft_type", incident.aircraft_type),("pilot_percent_hours_on_type",str(incident.pilot_percent_hours_on_type)),("pilot_total_hours",str(incident.pilot_total_hours)),

("midair", str(int(incident.midair)))):

element.setAttribute(attribute, value)

for name, text in (("airport", incident.airport),("narrative", incident.narrative)):

text_element = tree.createTextNode(text)

name_element = tree.createElement(name)

name_element.appendChild(text_element)

element.appendChild(name_element)

root.appendChild(element)

该方法从获取一个DOM实现开始,默认情况下,DOM实现是由expat XML分析器提供的,xml.dom.minidom模块提供了一个比xml.dom模块所提供的更简单、更短小的DOM实现,尽管该模块使用的对象来自于xml.dom模块。获取了 DOM实现后,我们可以创建一个文档。xml.dom.DOMImplementation.createDocument()的第一个参数是名称空间URI——我们并不需要,因此将其赋值为None;第二个参数是一个限定名(根元素的标签名);第三个参数是文档类型,同样,也将其赋值为None,因为 我们没有文档类型。在获取了表示文档的树之后,我们取回根元素,之后对所有事故记录进行迭代。

对每个事故记录,我们创建一个<incident>元素,对事故的每个属性,我们使用该属性名与值调用setAttribute()。就像元素树中一样,我们也不需要担心“&”、"<"与 “>”(或属性值中的引号)的转义问题。对机场与叙述性文本元素,我们必须创建一 个文本元素来存放文本,并以一个通常的元素(带有适当的标签名)作为文本元素的父亲——之后,我们将该通常元素(及其包含的文本元素)添加到当前的事故元素中。 事故元素完整后,就将其添加到根。

fh = None

try:

fh = open(filename,"w", encoding="utf8")

tree.writexml(fh, encoding="UTF-8")

return True

我们没有给出except语句块以及finally语句块,因为这与我们前面已经看到的都是相同的。从上面的代码中可以清晰看到的是,内置的open()函数使用的编码字符串与用于XML文件的编码字符串之间的差别,这一点在前面也已讨论。

将XML文档导入到DOM中与导入到元素树中是类似的,但与从元素树中导出类似,导入到DOM也需要更多的代码。我们将分3个部分来査看import_xml_dom() 函数,下面先给出其def行以及嵌套的get_text()函数。

def import_xml_dom(self, filename):

def get_text(node_list):

text =[]

for node in nodelist:

if node.nodeType == node.TEXT_NODE:

text.append(node.data)

return "".join(text).strip()

get_text()函数在一个节点列表(比如某节点的子节点)上进行迭代,对每个文本节点,提取该节点的文本并将其附加到文本列表中。最后,该函数返回已收集到一个单独的字符串中的所有文本,并且剥离掉两端的空白字符。

try:

dom = xml.dom.minidom.parse(filename)

except (EnvironmentError,xml.parsers.expat.ExpatError) as err:

print("{0}: import error: {1}".format(os.path.basename(sys.argv[0]), err))

return False

使用DOM分析XML文件是容易的,因为模块为我们完成了所有困难的工作, 但是我们必须做好处理expat错误的准备,因为就像元素树一样,expat XML分析器也是DOM类使用的默认分析器。

DOM存在后,我们清空当前的事故记录数据,并对所有事故标签进行迭代。每次迭代时,我们都提取其属性,对日期、数值型以及布尔型等数据,我们都将其转换为适当的类型,就像使用元素树时所做的一样。使用DOM与使用元素树之间真正较大的区别是对文本节点的处理过程,我们使用xml.dom.Element.getElementsByTagName()方法获取给定标签名的子元素——<airport>与<narrative>,我们知道总是会有其中的一个,因此我们取每个类型的第一个(唯一的一个),之后使用嵌套的get_text()函数对这些标签的子节点进行迭代, 以便提取其文本。

self.dear()

for element in dom.getElementsByTagName("incident"):

try:

data = (}

for attribute in ("report_id", "date", "aircraft_id","aircraft_type","pilot_percent_hours_on_type","pilot_total_hours", "midair"):

data[attribute] = element.getAttribute(attribute)

data["date"] = datetime.datetime.strptime(data["date"], "%Y-%m-%d").date()

data["pilot_percent_hours_on_type"]=(float(data["pilot_percent_hours_on_type"])) data["pilot_total_hours"]=int(data["pilot_total_hours"])

data["midair"] = bool(int(data["midair"]))

airport = element.getElementsByTagName("airport")[0]

data["airport"] = get_text(airport.childNodes)

narrative = element.getElementsByTagName("narrative")[0]

data["narrative"] = get_text(narrative.childNodes)

incident = lncident(**data)

self[incident.report_id] = incident

except (ValueError, LookupError, IncidentError) as err:

print("{0}: import error: {1}".format(os.path.basename(sys.argv[0]), err))

return False

手动写入XML

将预存的元素树或DOM写成XML文档可以使用单独的方法调用完成。如果数据本身不是以这两种形式存在,我们就必须先创建元素树或DOM,之后直接写出数据会更加方便。

写XML文件时,我们必须确保正确地对文本与属性值进行了转义处理,并且写的是格式正确的XML文档。下面给出export_xml_manual()方法,该方法用于以XML格式写出事故数据。

正如本章中我们通常所做的一样,我们也忽略了except语句块与finally语句块。

我们使用UTF-8编码写文件,并且必须为内置的open()函数指定该编码方式。严格地说,我们并不需要在<?xml?>声明中指定该编码,因为UTF-8是默认的编码格式, 但我们更愿意清晰地指定。我们选择使用双引号(”)来封装所有属性值,并且,为方便起见,我们使用单引号来封装事故数据中的字符串,以避免对引号进行转义处理的需要。

def export_xml_manual(self, filename):

fh = None

try:

fh = open(filename, "w", encoding="utf8*')

fh.write('<?xml version="1.0" encoding=“UTF-8"?>\n')

fh.write("<incidents>\n")

for incident in self.values();

fh.write('<incident report_id={report_id}' 'date="{0.date!s}"',

'aircraft_id={aircraft_id}''aircraft_type={aircraft_type}'

'pilot_percent_hours_on_type="{0.pilot_percent_hours_on_type}"'

'pilot.total_hours="{0.pilot_total_hours}"'

'midair="{0.midair:d}">\n'

'<airport>{airport}</airport>\n'

'<narrative>\n{narrative}\n</narrative>\n'

'</incident\n'.format(incident,

report_id=xml.sax.saxutils.quoteattr(incident.report_id),

aircraft_id=xml.sax.saxutils.quoteattr(incident.aircraft_id),

aircraft_type=xml.sax.saxutils.quoteattr(incident.aircraft_type),

airport=xml.sax.saxutils.escape(incident.airport),

narrative="\n".join(textwrap.wrap(xml.sax.saxutils.escape(incident.narrative.strip()), 70))))

fh.write("</incidents>\n")

return True

sax.saxutils.quoteattr()函数与 sax.saxutils.escape()函数(我们使用这一函数处理XML文本,因为该函数可以正确地对“&”、"<”、">"等字符进行转义处理)类似, 此外,该函数还可以对引号进行转义(如果需要),并返回已经使用引号包含了的字符串,这也是为什么我们不需要对报告ID以及其他字符串属性值加引号的原因所在。

叙述性文本中插入的换行与文本包裹纯粹是为了装饰用的,其目的是为了使其更便于人的阅读和编辑,但也可以忽略。

以HTML格式写数据与以XML格式并没有太大的差别。convert-incidents.py程序包含的export_html()函数是一个简单的实例,这里没有给出该函数,因为其中没有什么新东西。

使用SAX分析XML

与元素树和D0M在内存中表示整个XML文档不同的是,SAX分析器是逐步读入并处理的,从而可能更快,对内存的需求也不那么明显。然而,性能上的优势不能仅靠假设,尤其是元素树与D0M都使用了快速的expat分析器。

在遇到开始标签、结束标签以及其他XML元素时,SAX分析器宣称“分析事件” 并进行工作。为处理那些我们感兴趣的事件,我们必须创建一个适当的处理者类,并提供某些预定义的方法,在匹配分析事件发生时,就会调用这些方法。最常实现的处理者是内容处理者,当然,如果我们需要更好的控制,提供错误处理者以及其他处理者也是可能的。

下面给出的是完整的import_xml_sax()方法,由于大部分工作都已经由自定义的 IncidentSaxHandler类实现,因此,这一方法的实现代码很短。

def import_xml_sax(selfT filename):

fh = None

try:

handler = IncidentSaxHandler(self)

parser = xml.sax.make_parser()

parser.setContentHandler(handler)

parser.parse(filename)

return True

except (EnvironmentError, ValueError, IncidentError, xml.sax.SAXParseException) as err:

print("{0}: import error:{1}".format(os.path.basename(sys.argv[0]), err))

return False

我们首先创建了要使用的处理者,之后创建一个SAX分析器,并将其内容处理者设置为我们刚创建的那个。之后,我们将文件名赋予分析器的parse()方法,如果没有分析错误产生,就返回True。

我们将self (也就是说,这个Incidentcollection dict子类)传递给自定义的Incident SaxHandler类的初始化程序。处理者清空旧的事故记录,之后随着对文件分析的进程建立起一个事故字典。分析完成后,该字典将包含读入的所有事故。

class IncidentSaxHandler(xml.sax.handler.ContentHandler):

def __init__(self, incidents):

super().__init__()

self.__data = {}

self.__text =""

self.__incidents = incidents

self.__incidents.clear()

自定义的SAX处理者类必须继承自适当的基类,这将确保对于任何我们没有重新实现的方法(因为我们不关心这些方法处理的分析事件),都会调用该方法的基类版本, 并且实际上不做任何处理。

我们首先调用基类的初始化程序。对所有子类而言,这通常是一种好的做法,尽管对直接的object子类而言这样做没有必要(但也没有坏处)。字典self.__data用于保存某个事故的数据,self.__text字符串用于存放机场名的文本信息或叙述性文本的文本信息,这依赖于我们当前正在读入的具体内容,self.__incidents字典是到IncidentCollection字典(对这一字典,处理者直接对其进行更新操作)的对象引用。(一种替代的设 计方案是将一个独立的字典放置在处理者内部,并在最后使用dict.clear()将其复制到 IncidentCollection之后调用 dict.update()。)

def startElement(self, name, attributes):

if name == "incident":

self.__data = {}

for key, value in attributes.items():

if key == "date”:

self.__data[key]= datetime.datetime.strptime(value, "%Y-%m-%d").date()

elif key == "pilot_percent_hours_on_type”:

self.__data[key] = float(value)

elif key == "pilot_total_hours":

self.__data[key] = int(value)

elif key == "midair":

self.__data[key] = bool(int(value))

else:

self.__data[key] = value

self.__text=""

在读取到开始标签及其属性的任何时候,都会以标签名以及标签属性作为参数来调用 xml.sax.handler.Content-Handler.startElement()方法。对航空器事故XML 文件,开始标签是<incidents>,我们将忽略该标签;<incident>标签,我们使用其属性来生成 self.__data字典的一部分;<airport>标签与<narrative>标签,两者我们都忽略。在读取到开始标签时,我们总是清空self.__text字符串,因为在航空器事故XML文件格式中, 没有嵌套的文本标签。

在IncidentSaxHandler类中,我们没有进行任何异常处理。如果产生异常,就将传递给调用者,这里也就是import_xml_sax()方法,调用者将捕获异常,并输出适当的错误消息。

def endElement(self, name):

if name == "incident":

if len(self.__data) != 9:

raise IncidentError("missing data")

incident = lncident(**self.__data)

self.__incidents[incident.report_id] =incident

elif name in frozenset({"airport", "narrative"}):

self.__data[name] = self.__text.strip()

self.__text = ""

读取到结束标签时,将调用 xml.sax.handler.ContentHandler.endElement()方法。如果已经到达某条事故记录的结尾,此时应该已具备所有必要的数据,因此,此时创建 一个新的Incident对象,并将其添加到事故字典。如果已到达文本元素的结尾,就像 self.__data字典中添加一个项(其中包含迄今为止累积的文本)。最后,我们清空 self.__text字符串,以备后面使用。(严格地说,我们也没必要对其进行清空,因为在获取开始标签时也可以清空该字符串,但对有些XML格式,清空该字符串会有一定 的作用,比如对标签可以嵌套的情况。)

def characters(self, text):

self.__text += text

读取到文本时,SAX 分析器将调用 xml.sax.handler.ContentHandler.characters()方法,但并不能保证对所有文本只调用一次该方法,因为文本可能以分块的形式出现, 这也是为什么我们只是简单地使用该方法来累积文本,而只有在读取到相关的结束标 然后才真正将文本放置到字典中。(一种更高效的实现方案是将self.__text作为一个列表,这一方法的主体部分则使用self.__text.append(text),其他方法也相应调整。)

与使用元素树或DOM相比,使用SAX API是非常不同的,但确实也是很有效的。 我们可以提供其他处理者,并在内容处理者中重新实现额外的方法,以便按我们的需要施加更多的控制。SAX分析器本身并不保存XML文档的任意表示形式——这使得 SAX适合于将XML读入到我们的自定义数据组合中,也意味着没有SAX “文档”以 XML格式写出,因此,对写XML而言,我们必须使用本章前面描述的某种方法。

语法 文件对象属性与方法

f.close() 关闭文件对象f,并将属性f.closed设置为True

f.closed 文件已关闭,则返回True

f.encoding bytes与str之间进行转换时使用的编码

f.fileno() 返回底层文件的文件描述符(只对那些有文件描述符的文件对象是可用的)

f.flush() 清空文件对象 f

f.isatty() 如果文件对象与控制台关联,就返回True (只有在文件对象应用了真正的文件时才是 可用的)

f.mode 文件对象打开时使用的模式

f.name 文件对象f的文件名(如果有)

f.newlines 文本文件f中的换行字符串的类型

f.__next__() 返回文件对象f的下一行,大多数情况下,这种方法是隐式地使用的,比如对f中的行

f.peek(n) 返回n个字节,而不移动文件指针的位置

f.read(count) 从文件对象f中读取至多count个字节,如果没有指定count,就读取从当前文件指针直 至最后的每个字节。以二进制模式读时,返回bytes对象;以文本模式读时,返回str 对象。如果没有要读的内容(已到文件结尾),就返回一个空的bytes或str对象

f.readable() 如果f已经打开等待读取,就返回True

f.readinto(ba) 将至多len(ba)个字节读入到bytearray ba中,并返回读入的字节数 如果在文件结尾,就为0 (只有在二进制模式时才是可用的)

f.readline(count) 读取下一行(如果指定count,并且在\n字符之前满足这一数值,那么至多读入count 个字节),包括\n

f.readlines(sizehint) 读入到文件结尾之前的所有行,并以列表形式返回。如果给定sizehint,那么读入大概 至多sizehint个字节(如果底层文件对象支持)

fseek(offset,whence) 如果没有给定whence,或其为os.SEEK_SET,就按给定的offset移动文件指针(并作 为下一次读、写得起点);如果whence为os.SEEK_CUR,就相对于当前文件指针位置 将其移动offset (可以为负值)个位置(whence为os.SEEK_END,则是相对文件结尾)。 在追加"a"模式下,写入总是在结尾处进行的,而不管文件指针在何处。在文本模式下,只应该使用tell()方法的返回值作为offset

f.seekable() 如果f支持随机存取,就返回True

f.tell() 返回当前指针位置(相对于文件起始处)

f.truncate(size) 截取文件到当前文件指针所在位置,如果给定size,就到size大小处

f.writable() 如果f是为写操作而打开的,就返回True

f.write(s) 将bytes/bytearray对象s写入到文件(该文件以二进制模式打开),或者将str对象s写入到文件(该文件以文本模式打开)

f.writelines(seq) 将对象序列(对文本文件而言是字符串,对二进制文件而言是字节字符串)写入到文件

以上内容部分摘自视频课程05后端编程Python15文件处理(下),更多实操示例请参照视频讲解。跟着张员外讲编程,学习更轻松,不花钱还能学习真本领。

发表回复