使用列表理解,如何在遇到某个 input/stop_word 之前获取用户输入?

Posted

技术标签:

【中文标题】使用列表理解,如何在遇到某个 input/stop_word 之前获取用户输入?【英文标题】:Using list comprehension, how to take user input until a certain input/stop_word is encountered? 【发布时间】:2021-02-16 11:32:26 【问题描述】:

我想继续接受用户的输入,直到用户输入某个字符串,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]']

此外,这是否可以应用于任何用户定义的函数,以便在返回特定值之前对其进行计算?

我检查了这些问题,但不符合我的要求:

How to take valid and specific number of inputs from the user using list comprehension as of the statement given below in python [duplicate]

这处理特定数量的输入,解决方案也是基于正则表达式解析,在这里不起作用。

Asking the user for input until they give a valid response 这使用了while,这正是我不想要的。此外,这个问题的动机更倾向于输入验证,而不是控制流。

【问题讨论】:

【参考方案1】:

iter 救援

尽管iter 主要与单个参数一起使用,但它有一个鲜为人知的第二个参数,它会改变函数的行为方式。

来自python docs of 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, 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 的最简单方法。但是,对于更复杂的需求,这并不是很有用。

【讨论】:

【参考方案2】:

takewhile 条件为真

我们可以使用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__,并将函数应用于下一个值,同时满足条件。

这似乎是做同一件事的一种多余且过于复杂的方法,但是,它的优点应该在以下示例中显而易见。

优点:

    takewhile 可用于测试多个 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']
    可以与rangeitertools.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)) 相同,但是当不需要循环变量时,后者是faster。

    可以与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,但可能会以可读性为代价。

【讨论】:

【参考方案3】:

构建自己的迭代器

另一种选择是制作自定义 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 无提示输入,直到输入 qQ
>>> 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']

为了灵活性和可读性,这个是最好的。但是,如果只有在整个程序中以不同的条件多次使用它才值得。

【讨论】:

以上是关于使用列表理解,如何在遇到某个 input/stop_word 之前获取用户输入?的主要内容,如果未能解决你的问题,请参考以下文章

在列表视图中使用具有更多视图的数组适配器

如何在python列表中查找某个元素的索引

Java反射理解-- 方法反射的基本操作

如何在VB中按某个组件对对象列表进行排序?

如果遇到某些条件,我如何通过节点列表进行迭代并替换内容?

如何理解虚拟机类加载过程详解