ホームページ  >  記事  >  データベース  >  range()とは何ですか?なぜ反復子を生成しないのでしょうか?

range()とは何ですか?なぜ反復子を生成しないのでしょうか?

不言
不言転載
2019-01-07 10:30:513964ブラウズ

この記事では、range() とは何ですか?なぜ反復子を生成しないのでしょうか?困っている友人は参考にしていただければ幸いです。

イテレータは 23 の設計パターンの中で最も一般的に使用されるものの 1 つであり、Python のいたるところでよく使用されますが、その存在は必ずしも認識されていません。イテレータに関する私のシリーズ (記事の最後にあるリンク) では、イテレータを生成する少なくとも 23 の方法について言及しました。いくつかのメソッドはイテレータを生成するために特別に使用されますが、他のメソッドは他の問題を解決するためにイテレータを「密かに」使用します。

システムがイテレータについて学習する前は、 range() メソッドはイテレータの生成にも使用されると常に考えていましたが、今になって突然、このメソッドはイテレータではなく反復可能なオブジェクトのみを生成することに気づきました。 (追記: Python2 の range() はリストを生成します。この記事は Python3 に基づいており、反復可能なオブジェクトを生成します。)

そこで、次の質問があります。なぜ range() は反復子を生成しないのですか?答えを探す過程で、範囲型についていくつかの誤解があることがわかりました。したがって、この記事では音域について包括的に理解することができます。私はあなたと一緒に学び、進歩できることを楽しみにしています。

1. range() とは何ですか?

その構文: range(start, stop [,step]); start はカウントの開始値を指します。デフォルトは 0 です。stop はカウントの終了値を指します。 stop; step はステップ サイズで、デフォルトは 1 で、0 にすることはできません。 range() メソッドは、左側が閉じていて右側が開いている整数の範囲を生成します。

>>> a = range(5)  # 即 range(0,5)
>>> a
range(0, 5)
>>> len(a)
5
>>> for x in a:
>>>     print(x,end=" ")
0 1 2 3 4

range() 関数については注意すべき点がいくつかあります。(1) 左閉および右開の間隔を表します。(2) 受け取るパラメータは整数である必要があります。負の数ですが、浮動小数点数やその他の型は使用できません。(3) 要素の判定、要素の検索、スライスなどの操作を実行できる不変のシーケンス型です。(4) 要素を変更することはできません。反復可能なオブジェクトですが、イテレータではありません。

# (1)左闭右开
>>> for i in range(3, 6):
>>>     print(i,end=" ")
3 4 5

# (2)参数类型
>>> for i in range(-8, -2, 2):
>>>     print(i,end=" ")
-8 -6 -4
>>> range(2.2)
----------------------------
TypeError    Traceback (most recent call last)
...
TypeError: 'float' object cannot be interpreted as an integer

# (3)序列操作
>>> b = range(1,10)
>>> b[0]
1
>>> b[:-3]
range(1, 7)
>>> b[0] = 2
TypeError  Traceback (most recent call last)
...
TypeError: 'range' object does not support item assignment

# (4)不是迭代器
>>> hasattr(range(3),'__iter__')
True
>>> hasattr(range(3),'__next__')
False
>>> hasattr(iter(range(3)),'__next__')
True

2. range() がイテレータを生成しないのはなぜですか?

イテレータを取得するための組み込みメソッドは数多くあります (zip()、enumerate()、map()、filter()、reversed() など)。ただし、range() のみです。反復可能オブジェクトのメソッドはユニークです (反例がある場合はお知らせください)。ここで私の知識に誤解があります。

for ループのトラバーサル中、反復可能オブジェクトとイテレーターのパフォーマンスは同じです。つまり、両方とも遅延評価され、空間計算量と時間計算量に違いはありません。私はかつて、この 2 つの違いを「同じものだが、異なるもの」と要約しました。同じことは、どちらも遅延反復できることですが、違いは、反復可能なオブジェクトが自己走査 (つまり next() メソッド) をサポートしていないことです。 )、イテレータ自体はスライスをサポートしていません (つまり、__getitem__() メソッド)。

これらの違いにもかかわらず、どちらが優れているかを結論付けるのは困難です。ここで微妙なのは、イテレータが 5 つの組み込みメソッドすべて用に設計されているのに、 range() メソッドが反復可能なオブジェクトになるように設計されているのはなぜでしょうか。全部統一したほうがいいんじゃないでしょうか?

実際、Python は標準化のためにこの種のことをたくさん行ってきました。たとえば、Python2 には range() と xrange() という 2 つのメソッドがありましたが、Python3 ではそのうちの 1 つが削除され、 「Li Dai Tao Jian」メソッドを使用しました。もっと形式的にして、 range() がイテレータを生成するようにしてはどうでしょうか?

この問題に関しては、公式の説明が見つかりませんでした。以下は純粋に 個人的な意見です。

zip() およびその他のメソッドは、特定の反復可能なオブジェクトのパラメーターを受け取る必要があり、これはオブジェクトを再処理するプロセスであるため、特定の結果をすぐに生成する必要があるため、Python 開発者は結果がイテレーターになるように設計します。 。これには、パラメーターとして使用される反復可能オブジェクトが変更された場合、結果として使用される反復子は消耗品であるため、誤って使用されないという利点もあります。

range() メソッドは異なります。受け取るパラメータは反復可能なオブジェクトではないため、直接使用することも、他の再処理に使用することもできる反復可能なオブジェクトとして設計されています。 。たとえば、zip() などのメソッドは範囲型パラメータを受け入れることができます。

>>> for i in zip(range(1,6,2), range(2,7,2)):
>>>    print(i, end="")
(1, 2)(3, 4)(5, 6)
言い換えれば、 range() メソッドは主要なプロデューサーであり、それが生成する原材料は、早い段階でイテレータに変換するのは間違いなく不要です。

この解釈は理にかなっていると思いますか?この話題についてお気軽に私と話し合ってください。

3. 範囲タイプとは何ですか?

上記は、「range() がイテレータを生成しない理由」に対する私の答えです。このアイデアに従って、生成された範囲オブジェクトを研究しました。調査の結果、この範囲オブジェクトは単純ではないことがわかりました。

最初に奇妙なのは、それが不変のシーケンスであるということです。これには気づきませんでした。 range() の値を変更することを考えたことはありませんでしたが、この変更不可能な機能にはやはり驚きました。

ドキュメントに目を通すと、正式な区分は次のとおりです。 -

基本的なシーケンス タイプには、リスト、タプル、範囲オブジェクトの 3 つがあります。 (基本的なシーケンス タイプには、リスト、タプル、範囲オブジェクトの 3 つがあります。)

这我倒一直没注意,原来 range 类型居然跟列表和元组是一样地位的基础序列!我一直记挂着字符串是不可变的序列类型,不曾想,这里还有一位不可变的序列类型呢。

那 range 序列跟其它序列类型有什么差异呢?

普通序列都支持的操作有 12 种,在《你真的知道Python的字符串是什么吗?》这篇文章里提到过。range 序列只支持其中的 10 种,不支持进行加法拼接与乘法重复。

>>> range(2) + range(3)
-----------------------------------------
TypeError  Traceback (most recent call last)
...
TypeError: unsupported operand type(s) for +: 'range' and 'range'

>>> range(2)*2
-----------------------------------------
TypeError  Traceback (most recent call last)
...
TypeError: unsupported operand type(s) for *: 'range' and 'int'

那么问题来了:同样是不可变序列,为什么字符串和元组就支持上述两种操作,而偏偏 range 序列不支持呢?虽然不能直接修改不可变序列,但我们可以将它们拷贝到新的序列上进行操作啊,为何 range 对象连这都不支持呢?

且看官方文档的解释:

...due to the fact that range objects can only represent sequences that follow a strict pattern and repetition and concatenation will usually violate that pattern.

原因是 range 对象仅仅表示一个遵循着严格模式的序列,而重复与拼接通常会破坏这种模式...

问题的关键就在于 range 序列的 pattern,仔细想想,其实它表示的就是一个等差数列啊(喵,高中数学知识没忘...),拼接两个等差数列,或者重复拼接一个等差数列,想想确实不妥,这就是为啥 range 类型不支持这两个操作的原因了。由此推论,其它修改动作也会破坏等差数列结构,所以统统不给修改就是了。

4、小结

回顾全文,我得到了两个偏冷门的结论:range 是可迭代对象而不是迭代器;range 对象是不可变的等差序列。

若单纯看结论的话,你也许没有感触,或许还会说这没啥了不得啊。但如果我追问,为什么 range 不是迭代器呢,为什么 range 是不可变序列呢?对这俩问题,你是否还能答出个自圆其说的设计思想呢?(PS:我决定了,若有机会面试别人,我必要问这两个问题的嘿~)

由于 range 对象这细微而有意思的特性,我觉得这篇文章写得值了。本文是作为迭代器系列文章的一篇来写的,所以对于迭代器的基础知识介绍不多,欢迎查看之前的文章。另外,还有一种特殊的迭代器也值得单独成文,那就是生成器了,敬请期待后续推文哦~

以上がrange()とは何ですか?なぜ反復子を生成しないのでしょうか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。