python - 在 class 初始化或延迟时填充生成器?

python - populate generator on class init or defer?

我有一个 Django 应用程序,它在非常伪代码的级别上执行如下操作:

class SerialisedContentItem():

    def __init__(self, item),
        self.__output = self.__jsonify(item)

    def fetch_output(self):
        return self.__output

    def __jsonify(self, item):
        serialized = do_a_bunch_of_serialisey_stuff()
        return json.dumps(serialized)

所以基本上 - 一旦 class 被实例化,它:

然后它被用来生成这样的页面:

for item in page.items:
    json_item = SerialisedContentItem(item)
    yield json_item.fetch_output()

这对我来说似乎有点毫无意义。而且它还会导致我们需要进行的一些业务逻辑更改出现问题。

我更愿意做的是推迟 "jsonify" 函数的调用,直到我真正需要它为止。粗略地说,将上面的内容更改为:

class SerialisedContentItem():

    def __init__(self, item),
        self.__item = item

    def fetch_output(self):
        return self.__jsonify(self.__item):

这看起来更简单,并且与我的逻辑的混淆稍微少一些。

但是:有没有我没有看到的缺点?我的更改是否性能较差,或者不是一种好的做事方式?

只要每个项目只调用 fetch_output 一次,就不会影响性能(很明显,如果在同一个 SerializedContentItem 实例上调用 fetch_output 两次,性能就会受到影响).不做无用的操作通常也是一件好事(你不希望 open("/path/to/some/file.ext") 读取文件的内容,是吗?)

唯一需要注意的是,对于原始版本,如果 itemSerializedContentItem 的初始化和对 fetch_output 的调用之间发生变化,则不会反映更改在 json 输出中(因为它是在初始化时创建的),而对于您的 "lazy" 版本,这些更改将反映在 json 中。这是不行的、潜在的问题还是您真正想要的取决于上下文,所以只有您自己知道。

编辑:

what's prompted my question: according to my (poor) understanding of yield, using it here makes sense: I only need to iterate page items once, so do so in a way that minimises the memory footprint. But the bulk of the work isn't currently being done in the yield function, it's being done on the line above it when the class is instantiated, making yield a bit pointless. Or am I misunderstanding how it works?

恐怕你确实误会了yield。将 json 序列化推迟到 yield json_item.fetch_output()nothing (nada, zero, zilch, shunya) 更改为原始版本的内存消耗。

yield 不是一个函数,它是一个关键字。它所做的是将包含它的函数变成 "generator function" - 一个 returns 生成器(惰性迭代器)对象的函数,然后您可以对其进行迭代。它不会改变用于 json 化一个项目的内存,并且这个 json 化是否发生 "on the same line" 作为 yield 关键字完全无关。

生成器带给您的(wrt/内存使用)是您不必一次创建完整的内容列表,即:

def eager():
   result = []
   for i in range(1000):
       result.append("foo {}\n".format(i))
   return result

with open("file.txt", "w") as outfile:
    for item in eager():
        outfile.write(item)

这首先在内存中创建一个包含 1000 项的长列表,然后对其进行迭代。

def lazy():
   result = []
   for i in range(1000):
       yield "foo {}\n".format(i)

with open("file.txt", "w") as outfile:
    for item in lazy():
        outfile.write(item)

这会在每次迭代中懒惰地生成一个又一个字符串,因此您不会在内存中得到 1000 个项目列表 - 但您仍然生成了 1000 个字符串,每个字符串使用相同数量的 space与第一个解决方案一样。不同之处在于,因为(在这个例子中)你没有保留对这些字符串的任何引用,它们可以在每次迭代时被垃圾收集,而将它们存储在列表中会阻止它们被收集,直到列表本身没有更多引用.