问题描述
我想继续接受用户的输入,直到用户输入某个字符串,在list comprehension
中,如何实现?
我需要的相当于:
lst = []
stop = 'stop'
while True:
val = input()
if val == stop:
break
lst.append(val)
print('output:',lst)
# Output:
# 1
#
# hello
#
# three
#
# [1,2,3]
#
# stop
# output: ['1','hello','three','[1,3]']
但是,重申一下,我不想要while
,我更喜欢单线,例如:
stop = 'stop'
lst = [i for i in take_input_until(stop)]
print(lst)
# Output:
# 1
#
# hello
#
# three
#
# [1,3]']
此外,这是否可以应用于任何用户定义的函数,以便可以一直计算直到返回特定值?
我检查了这些问题,但不符合我的要求:
-
Asking the user for input until they give a valid response 这使用了 while,这正是我不想要的。此外,这个问题的动机更倾向于输入验证,而不是控制流。
解决方法
iter
来救援
尽管 iter
主要与单个参数一起使用,但它还有一个鲜为人知的第二个参数,这会改变函数的行为方式。
iter(object[,sentinel])
...如果给出了第二个参数 sentinel,则 object 必须是可调用对象。在这种情况下创建的迭代器将在每次调用 next() 方法时调用不带参数的对象;如果返回值等于 sentinel,则引发 StopIteration,否则返回值。
第二种形式的 iter() 的一个有用应用是构建一个块阅读器。例如,从二进制数据库文件中读取固定宽度的块,直到到达文件末尾:
from functools import partial
with open('mydata.db','rb') as f:
for block in iter(partial(f.read,64),b''):
process_block(block)
上面的例子也回答了第二个问题。还有其他方法可以实现这一目标。在以下示例中,我将说明在满足特定条件之前获取输入的不同方法,但是,这只是特殊情况,对于第二个问题,只是函数是 input
。所以下面的所有示例都可以用于任何其他功能。
这可用于获取 input
直到遇到特定输入:
>>> STOP = 'stop'
>>> lst = list(iter(input,STOP))
# can also be written as list comprehension,# which would be helpful if you want to do something with the values
#>> lst = [i for i in iter(input,STOP)]
1
hello
three
[1,2,3]
stop
>>> print(lst)
['1','hello','three','[1,3]']
这里的 iter(input,STOP)
是所谓的 callable_iterator
:
>>> type(iter(input,STOP))
callable_iterator
显示输入提示
为了显示每个输入的输入提示,我们可以使用functools.partial
:
>>> from functools import partial
>>> lst = [i for i in iter(partial(input,'enter: '),'x')] # or list(iter(partial(input,'x'))
enter: 1
enter: 2
enter: 3
enter: x
>>> lst
['1','2','3']
包括停用词
如果您还想在列表中包含停用词,您可以通过 *
运算符使用 iterable unpacking
:
>>> STOP = 'x'
>>> input_with_prompt = partial(input,'enter: ')
>>> lst = [*iter(input_with_prompt,STOP),STOP]
enter: 1
enter: 2
enter: 3
enter: x
>>> lst
['1','3','x']
这必须是替换 while 的最简单方法。但是,对于更复杂的需求,这不是很有用。
,
takewhile
条件为真
我们可以使用the Android docs for java.time.format.TextStyle
中的一个函数来检查输入是否等于停用词,如果不等于,则继续输入:
>>> from itertools import takewhile
>>> STOP = 'x'
>>> lst = list(takewhile(lambda inp: inp != STOP,iter(input_with_prompt,None)))
enter: 1
enter: 2
enter: 3
enter: x
>>> lst
['1','3']
这里 iter(input_with_prompt,None)
会继续调用 input
,因为它的 sentinel
参数永远不会被满足,因为 input
只返回 str
。这大致相当于一个 while True
循环,只是值是惰性计算的。
takewhile
将在 __next__
对象上调用 callable_iterator
,并在满足条件时将函数应用于下一个值。
这似乎是一种多余且过于复杂的做同一件事的方式,但是,在以下示例中,它的优势应该很明显。
优点:
- takewhile 可用于测试多个停用词:
>>> lst = list(takewhile(lambda inp: inp not in ['q','quit','Q'],None)))
enter: 1
enter: 2
enter: 3
enter: quit
>>> lst
['1','3']
- 可用于将多个值传递给函数,使用另一个
iterator
类型对象,这里是一个带有itertools.takewhile
的示例,用于在每次调用时为input
提供不同的参数.
>>> from itertools import count
>>> lst = list(takewhile(
... lambda inp: inp != STOP,... (input(f'enter val #{i} ("{STOP}" to quit): ') for i in count(1))
... ))
enter val #1 ("x" to quit): 1
enter val #2 ("x" to quit): 2
enter val #3 ("x" to quit): 3
enter val #4 ("x" to quit): x
>>> lst
['1','3']
- 可以与
itertools.count
或range
结合使用以在遇到stop_word
或输入数量达到特定值时停止。
>>> from itertools import repeat
>>> MAX_NUM = 5
>>> lst = list(takewhile(
... lambda inp: inp != STOP,... ((input('enter : ') for _ in range(MAX_NUM))
... ))
enter : 1
enter : 2
enter : 3
enter : 4
enter : 5
>>> lst
['1','4','5']
# ^ Here stop word is not encountered,# but input stops when MAX_NUM is reached.
#---------------------------------------------------#
>>> lst = list(takewhile(
... lambda inp: inp != STOP,... (input('enter : ') for _ in repeat(None,MAX_NUM))
... ))
enter : 1
enter : 2
enter : 3
enter : x
>>> lst
['1','3']
# ^ here stop word is encountered before MAX_NUM is reached.
注意: (f() for _ in range(n))
的行为与 (f() for _ repeat(None,n))
相同,但是当不需要循环变量时,后者是 itertools.repeat
。
- 可以与 faster 结合使用以获取多个停用词的嵌套
list
。
>>> list_of_list = list(list(val)
... for val in starmap(
... iter,... [
... (input_with_prompt,'q'),... (input_with_prompt,'quit'),'Q')
... ])
... ))
enter: 1
enter: 2
enter: q
enter: 2
enter: 3
enter: 4
enter: quit
enter: 5
enter: Q
>>> list_of_list
[['1','2'],['2','4'],['5']]
虽然这看起来像是一个非常神秘的用例,但它对于其他领域尤其有用,例如优化技术,您想检查使用不同超参数达到特定结果所采取的中间步骤。
因此为了灵活性,可以使用 takewhile
,但可能会牺牲可读性。
构建自己的迭代器
另一种选择是制作自定义 iterator
对象。
class IterWithCond:
"""Continuously call a function,until a given condition is met."""
def __init__(
self,func,cond,include_break=False,max_num=float('inf'),args=(),kwargs={}
):
self.func = func
self.cond = cond
self.args = args if isinstance(args,(list,tuple)) else (args,)
self.kwargs = kwargs
self.include = include_break
self.max_num = max_num
def __iter__(self):
self._count = 0
self._cond_met = False
return self
def __next__(self):
if self._cond_met or self._count >= self.max_num:
raise StopIteration
else:
out = self.func(*self.args,**self.kwargs)
self._cond_met = self.cond(out)
if not self.include and self._cond_met:
raise StopIteration
self._count += 1
return out
# Following line enables functionalities like `iter(IterWithCond(*args),stop)`
__call__ = __next__
您可以选择所需的配置。
- Vanila 输入没有提示,直到输入
q
或Q
。
>>> itr_obj = IterWithCond(
func=input,cond=lambda x: x.lower() == 'q',)
>>> lst = list(itr_obj)
1
2
3
q
>>> lst
['1','3']
- 添加提示,包括中断符
>>> itr_obj = IterWithCond(
func=input,include_break=True,args='enter: '
)
>>> lst = list(itr_obj)
enter: 1
enter: 2
enter: 3
enter: q
>>> lst
['1','q']
- 一直等到用户输入'q'/'Q'或直到用户输入5个数字,如果被停止字符停止,则不要包含停止字符。
>>> itr_obj = IterWithCond(
func=input,args='enter: ',max_num=5
)
>>> lst = list(itr_obj)
enter: 1
enter: 2
enter: 3
enter: 4
enter: 5
>>> lst
['1','5']
就灵活性和可读性而言,这是最好的。但如果只有在不同条件下在整个程序中多次使用它才值得。