いくつか回答がついてますが納得いったでしょうか?
自分はhayataka2049さん回答が本質をついていると思いますが、初心者の方に回答内容がつかみづらいかもしれないとも感じました。そこでhaytaka2049さんの回答の中身が「なぜ」という点をコメントしてみようと思います。
print(x)はどういう機能か?
実はprint関数自身は大した機能を持ってません。文字列(str型のインスタンス)を標準出力へ印字することぐらいです。しかしxに文字列以外を渡しても印字できますよね?それはprint関数が引数xに対してstr(x)を用いて文字列へ強制的に変換してから印字するからです。さらに
print(enumerate([1]))
と書くとenumerate([1])
を計算するのはprint関数の役目ではありません。Pythonのインタープリタは何かの関数を呼び出すとき、その引数をあらかじめ計算してから関数を呼び出します。
printはenumerateインスタンスが渡されたかlistインスタンスが渡されたかでなにか特別なことをやるわけではなく何が印字されるかはstr(printの引数)
の結果によって決まります。
さらにstr(x)
もxの型に応じてどんな文字列を結果とするか色々計算しているわけではありません。大雑把に言えば単にx.__str__()
というメソッド呼び出しをしているだけです。
これがhayataka2049さん回答(enumerate.__str__
がそう返しているから)の意味です。
なぜlistインスタンスをprintすると[1, 2]のように印字されるのか
[1, 2]
つまりlistインスタンスの__str__
メソッドの定義が次のようになっているからです。(本当の定義内容ではなく本件の説明のためのコードにしています)
python
1class list:
2 ...
3 def __str__(self):
4 s = ""
5 iterator = iter(self)
6 while True:
7 try:
8 s += ", " + repr(next(iterator))
9 except:
10 break
11 return "[" + s[2:] + "]"
repr
関数というのがでてきましたがこの関数は(今は)str
関数と似たようなものだと思ってください。repr(x)
もstr(x)
と同様、x.__repr__()
というメソッド呼び出しをした結果を返す関数です。
なぜlistインスタンスは要素をいちいち含めたような文字列を__str__
で返しているかといえば「そういう結果とした方が、このlistインスタンスがどんな内容になっているかプログラマーが把握しやすいから」と考えてください。Pythonの設計者がそうしたければ__str__
メソッドを"<list length=2>"
のような文字列を返す仕様にもできたはずです。ただそうすると実際の内容を把握するのに一々for文などを使って調べなければならないので「全部要素を含めるような文字列を返すことにしよう」と決めただけなのです。
なぜenumerate(...)をprintすると[(0, ...), (1, ..)]にならないか
さてenumerate([1, 2])
の結果はenumerate型のインスタンスになりますがenumerate型は質問者さんもおわかりのようにIteratorの一種になります。このenumerate型のインスタンスの__str__
メソッドは「将来nextが呼び出されたときに結果として返される値の列を印字することはしない」ということになっていますがなぜそうなっているか考えてみましょう。以下にenumerateとほぼ同じことができる別の定義を示します(実はenumerateの定義はこのくらい簡単です)。__str__
メソッドだけが本来のenumerateとは異なる定義になっている点にご注意を。
python
1class MyEnumerate: # 本来の定義を壊さないようにあえてクラス名(型名)を変えています。
2 def __init__(self, iterable):
3 self.iterator = iter(iterable)
4 self.ordinal = 0
5
6 def __next__(self): # next(このオブジェクト)とするとこのメソッドが呼び出される
7 value = (self.ordinal, next(self.iterator))
8 self.ordinal += 1
9 return value
10
11 def __iter__(self): # iter(このオブジェクト)とやるとこのメソッドが呼び出される
12 return self
13
14 # ここより上はenumerateインスタンスがiteratorとして振舞えるようにするための定義
15
16 def __str__(self): # 前述のlist.__str__と同じ内容です
17 s = ""
18 iterator = iter(self)
19 while True:
20 try:
21 s += ", " + repr(next(iterator))
22 except:
23 break
24 return "[" + s[2:] + "]"
25
26
27for i, e in MyEnumerate(['a', 'b']):
28 print('i={}, e={}'.format(i, e))
29
30# =>
31# i=0, e=a
32# i=1, e=b
33
34print(MyEnumerate(['a', 'b'])) # => [(0, 'a'), (1, 'b')]
上記のような定義だと質問者さんが一番最初に予想したとおりに印字されるます。しかし本物のenumerteはそうはしてません。next
を呼び出すと次々に異なる要素を返すことを思い出してください。つまり上記のような__str__
メソッドの定義をしてしまうと「printしただけなのに、MyEnumerateインスタンスの状態がかわってしまう」のです。__str__
や__repr__
はprintで印字する際などに多用するメソッドですが例えばデバッグのために印字したいだけなのに、デバッグプリントの行を挿入するとそれ以降の計算結果が変わってしまうと困りますよね。そういう意味合いで自分自身の状態を変えずに済む範囲でインスタンスを表す情報を文字列として返すのが__str__
や__repr__
の暗黙的なルールになっていると思います。
enumeratorだけでなく一般のIteratorインスタンスも同様で、自分自身の要素を得るには自分自身の状態を変化させねばなりませんので__str__
や__repr__
で生成する文字列に「自分が何を列挙しようとしているか」を含くめることはなく、結果の文字列に含めれらる情報はほとんどありません。せいぜい自分自身の型が何かをプログラマーにわかるようにしておくのが関の山です。しかしもしそうならわざわざ__str__
メソッドを定義する必要はなくなります。なぜなら「型名を結果とするような__str__
メソッドは既にobject型で定義されているのでenumerate自身でわざわざ定義するまでもない」からです。object.__str__
メソッドは
"<型名 object at 0xメモリーアドレス>"
というような文字列を返すように定義されてます。
enumerateは型ですが、Pythonの全ての型はobject型を継承してますのでobject型のインスタンスと同じ振る舞いでよいならメソッドを一々再定義しなくてよいのです。
上記がhayataka2049さんの回答にある
・enumerate.__str__
が定義されていない
・「どれくらい振る舞いが違うかというと、これくらい違いがあるのでけっこう注意が要る」
の背景です。
print(range(1, 11))はなぜ[1, 2, ..., 10]じゃないか
こちらもrangeインスタンスの__str__
メソッドがそういう内容を結果にしているのが直接的な原因ですが、質問者さんが期待するとおり別に[1, 2, 3, ..., 10]
と印字できないわけではありません。rangeインスタンスはIteratorではなくIterableでして前述のlist.__str__
と同じ定義にしてやりさえすればそういう結果が得られます。なぜそうしないかといえば「一々要素を文字列に含めなくても、開始・終了・ステップの各値を印字するだけでこのインスタンスが何者かを表すには充分だ」と設計者が考えたからだと思います。range(1, 10)
の結果は[1, 2, 3, ..., 10]
のような各要素を全部保持したような(つまりlistのような)インスタンスにはなってません。開始値、終了値、ステップの値程度を記録したごく小さなインスタンスなのです。rangeの実装例を挙げてみます。若干ややこしいかもしれませんがよく見ると単純な実装であることがわかります。(なお本件の説明のため大幅に省略した内容ですし正確でない点があります。)
Python
1class MyRange:
2 def __init__(self, start, stop, step):
3 self.start, self.stop, self.step = start, stop, step
4
5 def __iter__(self):
6 return MyRangeIterator(self)
7
8 def __str__(self):
9 return "MyRange({}, {}, {})".format(self.start, self.stop, self.step)
10
11class MyRangeIterator:
12 def __init__(self, range):
13 self.range = range
14 self.value = range.start
15
16 def __next__(self):
17 if self.value < self.range.stop:
18 self.value += self.range.step
19 return self.value - self.range.step
20 else:
21 raise StopIteration()
22
23 def __iter__(self):
24 return self
25
26for i in MyRange(1, 6, 2):
27 print(i)
28# =>
29# 1
30# 3
31# 5
32print(MyRange(1, 6, 2)) # => MyRange(1, 6, 2)
33print(iter(MyRange(1, 6, 2)) # => <__main__.MyRangeIterator object at 0x6fffabac0>
回答を書いてみて「どうも充分わかりやすく説明できてない」という感じは否めないです。Iterable/Iteratorの説明をするのにクラスや型の知識を前提とした説明になってしまっているからです。そのあたりは「雰囲気をなんとなくつかんでいただけたら」という思いで書いたのですが、かえってわかりづらかったらすみません。
なお上記に書いたことは既に質問者さんご自身の頭の中にほとんどあるような気もします・・・
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/02/11 13:15