我想继续接受用户的输入,直到用户在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, 2, 3]']
但是,重申一下,我不想要while
,我更喜欢一行之类的东西:
stop = 'stop'
lst = [i for i in take_input_until(stop)]
print(lst)
# Output:
# 1
#
# hello
#
# three
#
# [1, 2, 3]
#
# stop
# output: ['1', 'hello', 'three', '[1, 2, 3]']
此外,这是否可以应用于任何用户定义的函数,以便在返回特定值之前进行计算?
我已经检查了这些问题,这些问题不符合我的要求:
如何使用python〔duplicate〕中下面给出的语句的列表理解从用户那里获得有效和特定数量的输入
这涉及特定数量的输入,而且解决方案基于正则表达式解析,这在这里不起作用。
要求用户输入,直到他们给出有效的响应这使用while,这正是我不想要的东西。此外,这个问题的动机更倾向于输入验证,而不是控制流。
iter
前往救援
尽管iter
主要与单个参数一起使用,但它有一个鲜为人知的第二个参数,它改变了函数的行为方式。
来自iter:的python文档
iter(object[, sentinel])
。。。若给定了第二个参数sentinel,那个么对象必须是一个可调用的对象。在这种情况下创建的迭代器将在每次调用其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, 2, 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, 'enter: '), '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', '2', '3', 'x']
这必须是替换while的最简单方法。然而,对于更复杂的需求,这并不是很有用
takewhile
条件为True
我们可以使用itertools.takewhile
中的一个函数来检查输入是否等于停止字,如果不等于,则继续输入:
>>> 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', '2', '3']
这里iter(input_with_prompt, None)
将继续调用input
,因为它的sentinel
参数永远不会满足,因为input
只返回str
。这大致相当于while True
循环,只是值是延迟计算的。
当条件满足时,takewhile
将在callable_iterator
对象上调用__next__
,并在下一个值上应用该函数。
这可能看起来是一种多余的、过于复杂的方式来做同样的事情,然而,在下面的例子中,它的优势应该是显而易见的。
优点:
- takewile可用于测试多个stop_words:
>>> lst = list(takewhile(lambda inp: inp not in ['q', 'quit', 'Q'], iter(input_with_prompt, None)))
enter: 1
enter: 2
enter: 3
enter: quit
>>> lst
['1', '2', '3']
- 可用于使用另一个
iterator
类型的对象将多个值传递给函数,这里是一个带有itertools.count
的示例,用于在每次调用时为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', '2', '3']
- 可与
range
或itertools.repeat
组合,以在遇到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', '2', '3', '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', '2', '3']
# ^ here stop word is encountered before MAX_NUM is reached.
注意:(f() for _ in range(n))
的行为与(f() for _ repeat(None, n))
相同,但当不需要循环变量时,后者会更快。
- 可以与
itertools.starmap
组合以获得多个停止字的嵌套list
>>> list_of_list = list(list(val)
... for val in starmap(
... iter,
... [
... (input_with_prompt, 'q'),
... (input_with_prompt, 'quit'),
... (input_with_prompt, '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', '3', '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__
您可以选择所需的配置。
- 香草输入,无提示,直到输入
q
或Q
>>> itr_obj = IterWithCond(
func=input,
cond=lambda x: x.lower() == 'q',
)
>>> lst = list(itr_obj)
1
2
3
q
>>> lst
['1', '2', '3']
- 添加提示,包括中断字符
>>> itr_obj = IterWithCond(
func=input,
cond=lambda x: x.lower() == 'q',
include_break=True,
args='enter: '
)
>>> lst = list(itr_obj)
enter: 1
enter: 2
enter: 3
enter: q
>>> lst
['1', '2', '3', 'q']
- 直到用户输入'q'/'q'或直到用户输入5个数字,如果停止字符停止,则不包括停止字符
>>> itr_obj = IterWithCond(
func=input,
cond=lambda x: x.lower() == 'q',
args='enter: ',
max_num=5
)
>>> lst = list(itr_obj)
enter: 1
enter: 2
enter: 3
enter: 4
enter: 5
>>> lst
['1', '2', '3', '4', '5']
为了灵活性和可读性,这一个是最好的。但是,如果只有在不同条件下在整个程序中多次使用它才有价值