0

我目前支持通过创建单个标签对象生成所有 html 的旧版 python 应用程序。

我们有一个父标签类

class TAG(object):
    def __init__(self, tag="TAG", contents=None, **attributes):
        self.tag = tag
        self.contents = contents
        self.attributes = attributes

所以所有其他标签都继承自 TAG

class H1(TAG):
    def __init__(self, contents=None, **attributes):
        TAG.__init__(self, 'H1', contents, **attributes)
class H2(TAG):
    def __init__(self, contents=None, **attributes):
        TAG.__init__(self, 'H2', contents, **attributes)

主 TAG 类有一个 to_string 方法,类似于

def to_string(self):
    yield '<{}'.format(self.tag)
    for (a, v) in self.attr_g():
        yield ' {}="{}"'.format(a, v)
    if self.NO_CONTENTS:
        yield '/>'
    else :
        yield '>'
        for c in self.contents:
            if isinstance(c, TAG):
                for i in c.str_g():
                    yield i
            else:
                yield c
        yield '</{}>'.format(self.tag)

我们基本上写出了 to_string 方法的结果。

问题出现在生成大量 TAGS 并且大到足以造成性能影响的页面上。

我可以做些什么快速的胜利来让它表现得更好?

4

2 回答 2

3

前言:这是一种很糟糕的生成 HTML 的方式,但如果你要这样做,你最好还是尽可能用最好的方式来做。

python 非常擅长的一件事是字符串格式化。如果你要连接很多细小的字符串,你会从一开始就扼杀你的表现。您的to_string()方法应该看起来更像这样:

def to_string(self):
    return """<{tag}{attributes}>{content}</{tag}>""".format(
        tag=self.tag,
        attributes=' '.join('%s="%s"' % (attr, val) for
                            attr, val in self.attributes),
        content=''.join(
            (n if isinstance(n, basestring) else n.to_string()) for
            n in self.contents))

记下我在那里做过的几件事:

  1. 这是 Python,不是 Java。堆栈帧很昂贵,因此尽量减少函数和方法调用。
  2. 如果您不需要函数来抽象属性,请不要这样做。即:您不需要attr_g(除了可能进行转义,但是当您将数据放入时可以这样做)。
  3. 对同一个字符串进行所有字符串格式化!对一个小字符串进行单个字符串格式化操作,然后将其连接起来是一种巨大的浪费。
  4. 不要为此使用生成器。每次屈服时,您都在纠结于指令指针,这本质上会减慢速度。

其他指针:

  • 您继承自object,因此请使用该super()功能。
  • 不要浪费代码编写构造函数来声明标签类型:

    class TAG(object):
        def __init__(self, contents=None, **attributes):
            self.contents = contents
            self.attributes = attributes
    
    class H1(TAG):
        tag = 'H1'
    
    class H2(TAG):
        tag = 'H2'
    
  • StringIO如果你做了很多这样的事情,你可能会在对象方面取得一些成功。它们会让你构建你的标签和.write()它们。你可以把它们想象成 .NetStringBuffer或 Java 的StringBuilder

于 2013-01-11T01:49:11.650 回答
1

@mattbasta 在这里有正确的想法。但是,我想提出一些不同的建议:to_string使用cElementTree.TreeBuilder. 我不知道 ElementTree 的超快速序列化是否会胜过创建 ElementTree 的开销。

这是一个不稳定的TAG类,它的to_string_b()方法利用了一些微优化并使用 TreeBuilder 来构建树。(您的和 TreeBuilder 之间可能的重要区别to_string()是 TreeBuilder 将始终转义 XML 的输出,而您的则不会。)

import xml.etree.cElementTree as ET

class TAG(object):
    def __init__(self, tag="TAG", contents=None, **attributes):
        self.tag = tag
        # this is to insure that `contents` always has a uniform
        # type.
        if contents is None:
            self.contents = []
        else:
            if isinstance(contents, basestring):
                # I suspect the calling code passes in a string as contents
                # in the common case, so this means that each character of
                # the string will be yielded one-by-one. let's avoid that by
                # wrapping in a list.
                self.contents = [contents]
            else:
                self.contents = contents
        self.attributes = attributes

    def to_string(self):
        yield '<{}'.format(self.tag)
        for (a, v) in self.attributes.items():
            yield ' {}="{}"'.format(a, v)
        if self.contents is None:
            yield '/>'
        else :
            yield '>'
            for c in self.contents:
                if isinstance(c, TAG):
                    for i in c.to_string():
                        yield i
                else:
                    yield c
            yield '</{}>'.format(self.tag)

    def to_string_b(self, builder=None):
        global isinstance, basestring
        def isbasestring(c, isinstance=isinstance, basestring=basestring):
            # some inlining
            return isinstance(c, basestring)
        if builder is None:
            iamroot = True
            builder = ET.TreeBuilder()
        else:
            iamroot = False #don't close+flush the builder
        builder.start(self.tag, self.attributes)
        if self.contents is not None:
            for c in self.contents:
                if (isbasestring(c)):
                    builder.data(c)
                else:
                    for _ in c.to_string_b(builder):
                        pass
        builder.end(self.tag)
        # this is a yield *ONLY* to preserve the interface
        # of to_string()! if you can change the calling
        # code easily, use return instead!
        if iamroot:
            yield ET.tostring(builder.close())


class H1(TAG):
    def __init__(self, contents=None, **attributes):
        TAG.__init__(self, 'H1', contents, **attributes)
class H2(TAG):
    def __init__(self, contents=None, **attributes):
        TAG.__init__(self, 'H2', contents, **attributes)    

tree = H1(["This is some ", H2("test input", id="abcd", cls="efgh"), " and trailing text"])

print ''.join(tree.to_string())
print ''.join(tree.to_string_b())
于 2013-01-11T02:32:01.313 回答