首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >扩展lxml.etree的功能

扩展lxml.etree的功能
EN

Code Review用户
提问于 2016-02-10 22:47:26
回答 1查看 1.1K关注 0票数 8

我编写了一个类来稍微定制lxml.etree.ElementTree的行为,并且我相当广泛地使用它。它工作得很好,但是有一些方法我不知道我是怎么写的,还有一些其他的方法看起来非常多余。下面我将详细说明具体问题,但首先是代码。

代码语言:javascript
复制
import lxml.etree as old_etree
from xml_utilities import clean_xml
from datetime import datetime

VERSION = 'X.X.X.X'
TOOL = 'ExampleTool'

class etree(old_etree._ElementTree):
  @staticmethod
  def parse(path):  
    old_tdx = old_etree.parse(path)
    new_tdx = etree(old_tdx)
    new_tdx._setroot(old_tdx.getroot())
    return new_tdx

  @staticmethod
  def fromstring(string):
    old_tdx = old_etree.fromstring(string)
    new_tdx = etree(old_tdx.getroottree())
    new_tdx._setroot(old_tdx)
    return new_tdx

  @staticmethod
  def getISOTime():
    iso_time = datetime.now().isoformat()
    format_time = iso_time.split('.')[0] + 'Z'
    return format_time

  @staticmethod
  def SubElement(*args, **kargs):
    return old_etree.SubElement(*args, **kargs)

  @staticmethod
  def Element(*args, **kargs):
    return old_etree.Element(*args, **kargs)

  def getMetaNode(self, name, default=''):
    metas = self.findall('meta')
    for meta in metas:
      if meta.get('name') == name:
        return meta
    return old_etree.SubElement(self.getroot(), 'meta', 
                                attrib={'name': name, 'value': default})

  def setMetaNode(self, name, value):
    node = self.getMetaNode(name)
    node.set('value', value)

  def write(self, path, updateTool=True):       
    self.setMetaNode('saved', etree.getISOTime())

    if updateTool:
      self.setMetaNode('version', VERSION)
      self.setMetaNode('type', TOOL)

    super(etree, self).write(path)
    clean_xml(path)

函数clean_xml是自定义编写的,并且在很大程度上不相关:它逃避了ElementTree.write默认不会遇到的麻烦字符(这是我们使用的文件规范的一个问题,而不是lxml)。关于问题:

  1. 是否有比从lxml.etree继承更好的方法来扩展_ElementTree的功能?最后,我希望更改方法ElementTree.write的行为,并添加ElementTree.getMetaNodeElementTree.setMetaNode方法。
  2. 是否有更好的方法来集成parsefromstringSubElementElement方法,而不是仅仅创建引用原始方法的瘦包装?
  3. 有人知道我的方法parsefromstring在做什么吗?我知道我的方法fromstringlxml.etree.fromstring有不同的功能,因为我希望它返回一个ElementTree而不是一个Element。这些方法主要是通过猜测和检查(oops)编写的。
  4. 是否有更好的方法来编写return old_etree.SubElement(...)行,这样它就不必占用两行了?这不是什么大事但是..。

最初,我试图直接覆盖lxml.etree.ElementTree.write,但这会引发错误AttributeError: 'lxml.etree._ElementTree' object attribute 'write' is read-only。一般的批评也受到欢迎。我知道我应该在这里发表评论,但是除了我不明白的两种方法之外,一切都是非常直接的。

另外,我不确定是否有人会认为使用etree这个名称来覆盖lxml.etree是个坏主意。这样做的原因是,我只需将任何以前编写的文件顶部的import行从from lxml import etree更改为from my_lxml import etree

EN

回答 1

Code Review用户

回答已采纳

发布于 2016-02-24 13:37:24

样板

代码语言:javascript
复制
class etree(old_etree._ElementTree):
  @staticmethod
  def parse(path):  
    old_tdx = old_etree.parse(path)
    new_tdx = etree(old_tdx)
    new_tdx._setroot(old_tdx.getroot())
    return new_tdx

  @staticmethod
  def fromstring(string):
    old_tdx = old_etree.fromstring(string)
    new_tdx = etree(old_tdx.getroottree())
    new_tdx._setroot(old_tdx)
    return new_tdx

在那些样板函数中,我不知道"tdx“是什么意思。我想它是链接到您使用的文件规范。

这些函数是我在代码中遇到的主要问题。您将lxml.etree模块与lxml.etree.ElementTree类混合使用。这四种方法是模块的方法,而不是类!我觉得你应该:

  1. 有一个真正的etree模块
  2. 将当前的etree类(重命名为ElementTree)放入其中
  3. 将这些函数添加到模块中,而不是类中。
  4. 修正返回类型以返回lxml返回的内容

这将使事情变得不那么混乱,因为它更接近于lxml.etree和标准xml.etree的工作方式。您的同事将能够更快地选择您的代码。

您将执行from lxml.etree import *,并且只重新定义要重新定义的函数。您可能不能期望获得完美的兼容性,但至少基本的API是相同的。

除了返回类型之外,对于函数的实现,我没有什么可说的:丑陋的,但我没有找到让事情变得更好的方法。是的,很容易理解它们。

代码语言:javascript
复制
  @staticmethod
  def SubElement(*args, **kargs):
    return old_etree.SubElement(*args, **kargs)

  @staticmethod
  def Element(*args, **kargs):
    return old_etree.Element(*args, **kargs)

你不再需要用我上面的建议来处理这个问题了。

getISOTime

代码语言:javascript
复制
  @staticmethod
  def getISOTime():
    iso_time = datetime.now().isoformat()
    format_time = iso_time.split('.')[0] + 'Z'
    return format_time

添加'Z‘意味着这是一个UTC时间,但这并不是因为您使用了datetime.now()而不是datetime.utcnow()。我住在UTC+4,你要返回的format_time对我来说是4个小时的假期。

getMetaNode

代码语言:javascript
复制
  def getMetaNode(self, name, default=''):
    metas = self.findall('meta')
    for meta in metas:
      if meta.get('name') == name:
        return meta

如果您想避免循环:使用lxml XPath支持,可以在这里使用meta.xpath("meta[name = $name]", name=name)

代码语言:javascript
复制
  def write(self, path, updateTool=True):       
    self.setMetaNode('saved', etree.getISOTime())

    if updateTool:
      self.setMetaNode('version', VERSION)
      self.setMetaNode('type', TOOL)

    super(etree, self).write(path)
    clean_xml(path)

吹毛求疵:在ElementTree上进行清理将防止您的文件系统看到两个不同的版本。假设在某个时候您决定使用看门狗,回调将在您有机会运行clean_xml之前启动,这可能会导致微妙的bug。

回答你的问题

  1. 是否有比从lxml.etree继承更好的方法来扩展_ElementTree的功能?最后,我希望更改方法ElementTree.write的行为,并添加ElementTree.getMetaNodeElementTree.setMetaNode方法。

因为lxml是用Cython编写的,所以我认为您不能直接将lxml设置为monkeypatch lxml,所以子类化是可行的方法。lxml可以选择使用__new__来简化事情,就像numpy为促进子类化所做的那样。

  1. 是否有更好的方法来集成parsefromstringSubElementElement方法,而不是仅仅创建引用原始方法的瘦包装?

见上面的“样板”。

  1. 有人知道我的方法parsefromstring在做什么吗?我知道我的方法fromstringlxml.etree.fromstring有不同的功能,因为我希望它返回一个ElementTree而不是一个Element。这些方法主要是通过猜测和检查(oops)编写的。

为什么要获得一个ElementTree而不是一个元素?我认为这是个坏主意,有两个原因:

  1. 从元素中获取ElementTree非常容易。
  2. 在使用lxml时,已经很难推理得到的对象类型,如果我们不能使用有关ElementTree/lxml的知识,情况只会变得更糟。

  1. 是否有更好的方法来编写return old_etree.SubElement(...)行,这样它就不必占用两行了?这不是什么大事但是..。

如果有的话,我想我会用两行以上的。如果你不想再担心这样的事情,我强烈建议雅普夫

最初,我试图直接覆盖lxml.etree.ElementTree.write,但这会引发错误AttributeError: 'lxml.etree._ElementTree' object attribute 'write' is read-only。一般的批评也受到欢迎。我知道我应该在这里发表评论,但是除了我不明白的两种方法之外,一切都是非常直接的。

是的,你不能把它关起来(就像在回答1中说的那样),但这不是你想要做的,因为你也想保留原来的实现。一个装潢师也不会改进东西的。

命名

另外,我不确定是否有人会认为使用etree这个名称来覆盖lxml.etree是个坏主意。这样做的原因是,我只需将任何以前编写的文件顶部的import行从from lxml import etree更改为from my_lxml import etree

不,我认为用etree这个名字是个好主意。在Python中提供替换是一种常见的方法,标准库xml.etree就是这样做的。然而,你需要做正确的事情。同样,请参阅上面的“样板”部分。

哦,PEP 8建议不要在模块名中使用下划线,除非它提高了可读性,但我认为mylxml足够可读性。但是,简单地在模块前面添加my并不是一个好主意,因为您只是失去了一个机会来解释什么使您的lxml与众不同。既然它似乎特定于您使用的文件规范,那么为什么不在名称中使用它呢?

票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/119559

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档