一个 Python 函数可以同时是生成器和“非生成器”吗?

huangapple go评论82阅读模式
英文:

Can a python function be both a generator and a "non-generator"?

问题

  1. 我有一个函数我想要从中生成字节生成器行为),并根据`save`布尔值是否设置来写入文件非生成器行为)。这是否可能
  2. ```python
  3. def encode_file(source, save=False, destination=None):
  4. # 每次处理输入文件的内容3字节
  5. print('hello')
  6. with open(source, 'rb') as infile:
  7. # 将字节保存到目标文件
  8. if save:
  9. print(f'saving to file {destination}')
  10. with open(destination, 'wb') as outfile:
  11. while (bytes_to_encode := infile.read(3)):
  12. l = len(bytes_to_encode)
  13. if l < 3:
  14. bytes_to_encode += (b'\x00' * (3 - l))
  15. outfile.write(bytes_to_encode)
  16. return
  17. # 向调用者生成字节
  18. else:
  19. while (bytes_to_encode := infile.read(3)):
  20. l = len(bytes_to_encode)
  21. if l < 3:
  22. bytes_to_encode += (b'\x00' * (3 - l)) # 如果不足3字节则填充位
  23. yield encode(bytes_to_encode)
  24. return

在上面的实现中,该函数始终表现为生成器。当我调用

  1. encode_file('file.bin', save=True, destination='output.base64')

它不会打印“hello”,而是返回一个生成器对象。这对我来说没有意义。不应该打印“hello”,然后将控制传递到代码的if save:部分,从而避免完全生成字节的部分吗?

  1. <details>
  2. <summary>英文:</summary>
  3. I have a function which I want to yield bytes from (generator behaviour) and also write to a file (non-generator behaviour) depending on whether the `save` boolean is set. Is that possible?
  4. ```python
  5. def encode_file(source, save=False, destination=None):
  6. # encode the contents of an input file 3 bytes at a time
  7. print(&#39;hello&#39;)
  8. with open(source, &#39;rb&#39;) as infile:
  9. # save bytes to destination file
  10. if save:
  11. print(f&#39;saving to file {destination}&#39;)
  12. with open(destination, &#39;wb&#39;) as outfile:
  13. while (bytes_to_encode := infile.read(3)):
  14. l = len(bytes_to_encode)
  15. if l &lt; 3:
  16. bytes_to_encode += (b&#39;\x00&#39; * (3 - l))
  17. outfile.write(bytes_to_encode)
  18. return
  19. # yield bytes to caller
  20. else:
  21. while (bytes_to_encode := infile.read(3)):
  22. l = len(bytes_to_encode)
  23. if l &lt; 3:
  24. bytes_to_encode += (b&#39;\x00&#39; * (3 - l)) # pad bits if short
  25. yield encode(bytes_to_encode)
  26. return

In the above implementation, the function always behaves as a generator. When I call

  1. encode_file(&#39;file.bin&#39;, save=True, destination=&#39;output.base64&#39;)

it does not print "hello" instead, it returns a generator object. This does not make sense to me. Shouldn't "hello" be printed and then shouldn't control be directed to the if save: portion of the code thus avoiding the part of the function that yields completely?

答案1

得分: 3

一个函数既不能是生成器,也不能不是生成器,但当然你可以通过定义一个辅助函数来决定是否返回一个生成器对象。为了避免在两者之间重复(读取)使用with(并减少冗余),将一个分支作为另一个的客户端:

  1. def encode_file(source, save=False, destination=None):
  2. # 每次处理输入文件的内容3字节
  3. print('hello')
  4. # 将字节保存到目标文件
  5. if save:
  6. print(f'保存到文件 {destination}')
  7. with open(destination, 'wb') as outfile:
  8. for bytes_to_encode in encode_file(source):
  9. outfile.write(bytes_to_encode)
  10. # 将字节提供给调用者
  11. else:
  12. def g():
  13. with open(source, 'rb') as infile:
  14. while (bytes_to_encode := infile.read(3)):
  15. l = len(bytes_to_encode)
  16. if l < 3:
  17. bytes_to_encode += (b'\x00' * (3 - l)) # 如果字节不足,填充位
  18. yield encode(bytes_to_encode)
  19. return g()

(感谢interjay指出了在g中需要使用with的需要。)

英文:

A function can’t be a generator and also not be one, but of course you can decide whether to return a generator object or not by defining a helper function. To avoid duplicating the (read) with between the two (and reduce redundancy in general), make one branch a client of the other:

  1. def encode_file(source, save=False, destination=None):
  2. # encode the contents of an input file 3 bytes at a time
  3. print(&#39;hello&#39;)
  4. # save bytes to destination file
  5. if save:
  6. print(f&#39;saving to file {destination}&#39;)
  7. with open(destination, &#39;wb&#39;) as outfile:
  8. for bytes_to_encode in encode_file(source):
  9. outfile.write(bytes_to_encode)
  10. # yield bytes to caller
  11. else:
  12. def g():
  13. with open(source, &#39;rb&#39;) as infile:
  14. while (bytes_to_encode := infile.read(3)):
  15. l = len(bytes_to_encode)
  16. if l &lt; 3:
  17. bytes_to_encode += (b&#39;\x00&#39; * (3 - l)) # pad bits if short
  18. yield encode(bytes_to_encode)
  19. return g()

(Thanks to interjay for pointing out the need for the with in g.)

答案2

得分: 2

你不能即时创建一个函数生成器,但你可以将逻辑提取到一个生成器函数中,然后创建一个单独的函数,该函数要么返回该生成器,要么将其保存到文件中。以下是一个用于演示目的的简单示例,带有注释:

  1. # 作为生成器的示例函数
  2. # 在这里放入所有的逻辑
  3. def getNums_gen(n):
  4. for i in range(n):
  5. yield i
  6. def getNums(n, asList=False):
  7. gen = getNums_gen(n)
  8. # 如果不想保存它,只需返回生成器
  9. if not asList:
  10. return gen
  11. # 如果想保存它,循环遍历生成器并执行保存数据所需的任何操作。
  12. # 在此示例中,我们将其保存为列表,但也可以写入文件
  13. lst = [num for num in gen]
  14. return lst
  15. print(getNums(5, asList=True))
  16. print(getNums(5, asList=False))
英文:

You can't make a function generator on the fly, but you can extract the logic into a generator function and then create a separate function that will either return that generator or save it to a file. Here's a simpler example for demonstration purposess with comments:

  1. # example function as a generator
  2. # put all your logic here
  3. def getNums_gen(n):
  4. for i in range(n):
  5. yield i
  6. def getNums(n, asList=False):
  7. gen = getNums_gen(n)
  8. # if we don&#39;t want to save it, just return the generator
  9. if not asList:
  10. return gen
  11. # if we want to save it, loop through the generator and do whatever
  12. # is needed to save the data. in this example, we&#39;ll just save it as a
  13. # list, but it could be written to a file too
  14. lst = [num for num in gen]
  15. return lst
  16. print(getNums(5, asList=True))
  17. print(getNums(5, asList=False))

huangapple
  • 本文由 发表于 2023年5月28日 23:42:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/76352280.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定