问题 使用生成器来编写文件的标题和正文是pythonic吗?


如果我要用这个内容写一个文件:

#You have been defeated!
#It's merely a flesh wound!
We are the knights who say Ni!
We are the knights who say Ni!
We are the knights who say Ni!

那么用发送器使用发生器做它会非常非pythonic吗?我从未见过其他地方使用的发电机。

def write(file, header):

    with open(file,'w') as f:
        f.write(header)
        line = (yield)
        while True:
            f.write(line)
            line = (yield)

    return

file='holygrail.txt'
header="#You have been defeated!\n#It's merely a flesh wound!\n"
generator = write(file,header)
generator.send(None)
for i in range(3):
    generator.send('We are the knights who say Ni!\n')
generator.close()

我问,因为上面的方法对我非常有益,而不是在contextlib堆栈中打开多个不同的文件流。如果我像这样编写我的文件,我根本不必使用contextlib模块。

我之前从未问过这样的问题,我不知道它是否属于stackoverflow。


3214
2018-03-27 13:43


起源

我不知道发电机可以像这样使用!我不知道为什么这被投了票。 - Jayanth Koushik
我在这里可以问什么主题? - Bonifacio2
我认为 file 对于参数/变量来说,它不是一个非常幸运的选择,因为它是一个 内置功能。 - Frerich Raabe
@ tommy.carstensen,既然你有工作代码,我建议你在codereview.stackexchange.com/上提出这样的问题 - wnnmaw
@FrerichRaabe:实际上, file  不 Python 3中的内置函数,请参阅 这里。所以现在我认为它通常是最好的名字,或者至少是一个体面的名字。 - DSM


答案:


我喜欢你的解决方案的创造力,但我的主观意见是使用 contextlib.ExitStack() 因为每个发电机都需要准备好,所以看起来比使用发电机更清洁,更具可读性 generator.send(None) 并明确关闭。


顺便说一句,(尽管我认为 contextlib 将导致更短,更易读的代码), write 可以简化一点:

def write(file, header):
    with open(file, 'w') as f:
        f.write(header)
        while True:
            line = (yield)
            f.write(line)
    return

请注意,您只需要一个 line = (yield) 而不是两个。

而且,而不是启动发电机 generator.send(None) 你可以使用 coroutine 装饰:

def coroutine(func):
    """ http://www.python.org/dev/peps/pep-0342/ """
    def wrapper(*args, **kw):
        gen = func(*args, **kw)
        gen.send(None)
        return gen
    return wrapper

这是一个通常被理解的习语(PEP0342大卫比兹利谈话)用于将发电机转变为协程。所以用它装饰你的发电机也可以用来做广告 write 是一个协程。


10
2018-03-27 13:47



做generator.close()就足够了吗? - tommy.carstensen
@ tommy.carstensen:是的,那也行,但那真的是“Pythonic”吗?似乎没有任何好处 f=open(...); f.write(...); f.close()。 - unutbu
我不会将contextlib.ExitStack()作为堆栈来执行:f = stack.enter_context(open(...))以执行清理代码?我使用contextlib,因为我需要打开多个文件。如果我可以在生成器中打开每个文件,那将非常优雅。 - tommy.carstensen
我刚看到你的编辑。我不知道,我可以做“用f,g1,g2”。如果我必须打开数百个文件,它仍然会变得丑陋。 - tommy.carstensen
我喜欢你的解决方案的创造力,但我的主观意见是使用 contextlib.ExitStack() 因为每个发电机都需要准备好,所以看起来比使用发电机更清洁,更具可读性 generator.send(None) 并明确关闭。 - unutbu


我认为这个问题有点主观,但我相信“Pythonic”也意味着保持简单。而对于你的特殊情况,这可能是一些事情

open("blah.txt", 'w').write("""\
#You have been defeated!
#It's merely a flesh wound!
We are the knights who say Ni!
We are the knights who say Ni!
We are the knights who say Ni!
""")

我猜你的 实际 情况虽然不同......


0
2018-03-27 13:49





你的代码并不短,而且不比它更清晰

file='holygrail.txt'
header="#You have been defeated!\n#It's merely a flesh wound!\n"
with open(file, w) as fh:
    fh.write(header)
    for i in range(3):
        fh.write('We are the knights who say Ni!\n')

所以我不确定它的好处是什么。


0
2018-03-27 13:50



我想问题是真正的输入来自其他地方。 - ElmoVanKielmo
是的,真正的投入来自其他地方。来自一个文件的标题,可以很容易地保存在内存中,而来自多个其他文件的数据/正文,这些文件都太大而无法保存在内存中。 - tommy.carstensen


协程点是在.send()调用之间保存内部状态。

协同程序通常用于实现“消费者”模式(我很少使用它们来包装xlwt工作表:我需要跟踪行数以刷新它们)。没有任何状态你可以使用一个没有任何状态的简单函数(或文件对象的.write()方法)


0
2018-03-27 19:30