【问题标题】:Why iterator is considered functional-style in the Python documentation?为什么迭代器在 Python 文档中被认为是函数式的?
【发布时间】:2016-03-17 15:20:20
【问题描述】:

我正在尝试理解迭代器。我注意到Python documentation 认为迭代器是一种函数式构造。不是很懂。

迭代器里面不是有状态吗。所以当你调用it.__next__() 时,你改变了迭代器的状态。据我所知,对象的变异状态不被认为是函数式的,因为函数式编程强调对象/闭包的不变性和组合能力。

实际上,问题出现是因为我想编写一个接受令牌并返回迭代器的 Scheme 过程/函数。

(define tokens->iterator
  (lambda ls
    (lambda ()
      (if (null? ls)
          '*eoi*
          (let ((tok (car ls)))
            (set! ls (cdr ls))
            tok)))))

注意我必须使用set! 来改变ls,这就是我提出这个问题的方式。

要使用它,

(define it (tokens->iterator 1 '+ 2))

为了测试它,

scheme@(guile-user)> (it)
$2 = 1
scheme@(guile-user)> (it)
$3 = +
scheme@(guile-user)> (it)
$4 = 2
scheme@(guile-user)> (it)
$5 = *eoi*
scheme@(guile-user)> (it)
$6 = *eoi*

只是为了好玩,我还将它翻译成 Python:

def tokens_to_iterator(*tup):
    ls = list(tup)
    def iterator():
        if not ls:
            return "*eoi*"
        else:
            tok = ls.pop(0)
            return tok
    return iterator

类似地,pop() 方法通过改变列表来移除并返回第一个元素。

要使用它,

it = tokens_to_iterator(1, "+", 2)

为了测试它,

>>> it()
1
>>> it()
'+'
>>> it()
2
>>> it()
'*eoi*'
>>> it()
'*eoi*'

谁能澄清一下?顺便说一句,我正在使用 Python 3 和 Guile Scheme,以防有人有兴趣尝试这些示例。

【问题讨论】:

  • “功能”和“无可变状态”不是一回事。
  • 因为这个状态是隐藏在里面的,从外面无法访问;它是封装的。它会在每次 next 调用时自动更改,并保留对象的有效性。从外部观察者的角度来看,它的行为符合一些规律,是一致的,这不能从外部任意改变。这个状态只是一个实现细节。
  • @zwol 还有“无副作用”。它与“无可变状态”相同吗?为什么“功能”和“无可变状态”不一样?不是所有“函数式”语言都强调“无可变状态”吗?我在这里错过了什么吗?
  • @Will Ness OOP 不是也有数据封装吗?例如,状态被封装在一个对象中。您所能做的就是调用对象的方法。究竟是什么让迭代器发挥作用?
  • 有关相关示例,请参阅http://3e8.org/pub/scheme/doc/lisp-pointers/v1i4/p23-clinger.pdf 和作者对使用生成器问题的评论。对于两个消费者,任何一个观察到的序列都是混乱的。所以,这个脆弱的,取决于使用协议。该论文中的另一件事,流,实际上添加了 storage,生成器产生的结果被存储在其中,因此两个消费者都观察到相同的(共享)序列(因为它们从存储中提取,而不是从发电机本身)。这也是itertools.tee() 所做的,IIRC。或类似的东西。

标签: python iterator scheme


【解决方案1】:

你有一个很好的观点。迭代器当然不是“纯粹的功能性”,这个术语通常用来描述根本不使用突变的习语。不过,更广泛的术语“函数式”定义更宽松,表示使用相对较少突变的程序,这些程序使用高阶和一等函数,也许最广泛地说,“使用不看起来像 C。”

坦率地说,我认为我不会称迭代器为函数式。那就是:我同意你的看法。

【讨论】:

    【解决方案2】:

    函数式风格 是作为一个整体来处理数据列表,而不是你可以随心所欲地更改的值的集合。例如,如果您有一个数字列表,并且想要更改第 3 个元素,则非功能性方法是直接更改它:

    >>> lst = ["a", "b", "c", "d", "e"]
    >>> lst[3] = "Z"
    >>> lst
    ["a", "b", "c", "Z", "e"]
    

    函数式方法是编写一个函数,该函数采用原始序列并返回一个带有更改的新列表,而原始列表保持不变。

    >>> lst = ["a", "b", "c", "d", "e"]
    >>> new_lst = [x if i != 3 else "Z" for (i, x) in enumerate(lst)]
    >>> lst
    ["a", "b", "c", "d", "e"]
    >>> new_lst
    ["a", "b", "c", "Z", "e"]
    

    您的迭代器都不是纯粹的函数式,因为它们确实保持可变状态,尽管您可以将它们视为一个黑盒,因为迭代器的 用户 不能直接影响该状态。 p>

    纯函数式迭代器是一个函数,它将列表当前状态作为输入,并返回一个值一个新状态以传递给下一个函数的调用。

    >>> state = 0
    >>> def it(lst, state):
    ...   if state is None:
    ...       return None
    ...   return lst[state], state + 1
    ...
    >>> lst = ["a", "b", "c", "d", "e"]
    >>> value, new_state = it(lst, state)
    >>> value
    'a'
    >>> state, new_state
    (0, 1)
    >>> it(lst, new_state)
    ('b', 2)
    >>> state, new_state
    (0, 1)
    

    【讨论】:

    • 已修复。很难在 Python 中演示纯函数式技术,因为它没有 let 绑定来临时命名函数结果,这与变量赋值不太一样。 (最大的区别是您可以绑定一个名称一次;以后不能更改绑定。)
    • 我明白了,所以状态是显式传入的,使您的迭代器纯粹是功能性的,因为相同的lst 和相同的state,迭代器将返回相同的元素。我可以进一步声称 Python 的迭代器不是纯粹的函数,因为it.__next__() 将返回具有相同输入的不同元素(在这种情况下什么都不是),这是迭代器在内部维护状态的证据?
    • 正确。这就是为什么在将迭代器传递给 itertools.tee 之后,您应该只使用函数返回的迭代器之一,而不是原始迭代器本身。
    • (未来读者请注意:有人评论了我最初使用变量在对it 的连续调用之间传递状态;我的第一个评论和随后的编辑是对此的回应。我希望那个用户没有删除他的评论;这是一个有效的观点。)
    猜你喜欢
    • 2020-07-09
    • 2013-12-06
    • 1970-01-01
    • 2016-04-22
    • 1970-01-01
    • 2020-06-09
    • 2010-09-16
    • 2021-01-20
    • 1970-01-01
    相关资源
    最近更新 更多