質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.36%

  • Python 3.x

    8486questions

    Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

enumerate object at 0x102376318と表示されることから、enumerate()のtupleがどういう形式で戻り値となっているかを解釈したが、正しいか判断してほしい。

解決済

回答 4

投稿

  • 評価
  • クリップ 1
  • VIEW 287

gunmed

score 22

pythonの入門書を一冊やった程度のプログラミング初学者です。
enumerate()関数を理解するためいろいろとコードを組んで実験していました。
enumerate()は引数にシーケンスを入れると、その順番に0から番号をつけ、tupleとしてかえすという解釈です。
そして以下のコードを書きました。

branches = [11, 'aa', 'fa']


print(list(enumerate(branches)))
print(type(enumerate(branches)))
print(enumerate(branches))

for a, b in enumerate(branches):
    print(a, b)
[(0, 11), (1, 'aa'), (2, 'fa')]
<class 'enumerate'>
<enumerate object at 0x102376318>
0 11
1 aa
2 fa

問題はprint(enumerate(branches))で自分の中ではtupleが返されると思っていたので、<enumerate object at 0x102376318>が理解できませんでした。
(0, 11), (1, 'aa'), (2, 'fa')
のようにenumerate()で作成されたtupleの要素ごとに返されると思っていました。
ここで、tupleとして返すの返すがどういった形で返しているのか疑問になってしまいました。

<enumerate object at 0x102376318>となぜ表示されるのかを考察

そこで色々調べてみると結局enumerate()はイテレータで値を一つづつ取り出せるオブジェクトであることにたどり着きました。
listやrangeはイテレータが生成可能な関数らしいです。
<enumerate object at 0x102376318>の真相を知るべく、その3者を用いて以下のようなコードを作りました。

branches = [11, 'aa', 'fa']

# enumerateで実験
i = iter(enumerate(branches))

print(next(i))

o = enumerate(branches)

print(next(o))

print(enumerate(branches))
# rangeで実験

i = iter(range(1,11))

print(next(i))

print(range(1,11))

print(i)


# listで実験
s = iter(branches)

print(next(s))

print(branches)

print(s)
# enumerateの結果
(0, 11)
(0, 11)
<enumerate object at 0x106b19438>
# rangeの結果
1
range(1, 11)
<range_iterator object at 0x106afe9c0>
# listの結果
11
[11, 'aa', 'fa']
<list_iterator object at 0x106ad52b0>

気づいたことはenumerate()はイテレータを生成するための関数iter()を使用しなくてもイテレータとして存在しているということです。なぜなら、o = enumerate(branches)でprint(next(o))を用いて値を取り出せているからです。

また、enumerate object at 0x106b19438のような出力結果はイテレータがprint()の引数であると起こるようです。なぜなら、iter()でlistとrangeを囲んでイテレータに変換しprintすると、print(enumerate(branches))と似たような形式で出力されています。

上記の結果からまとめると、最初のコードでprint(enumerate(branches))で自分が考えた結果(tupleが列挙されるという結果)にならなかったのは、enumerate()はtupleを返すという関数であるが、それはデータ型ではなく、すでにイテレータとして返しているので、<enumerate object at 0x102376318>という結果になった。結果として、print(enumerate(branches))はprint()の引数がイテレータとなってしまったので、、<enumerate object at 0x102376318>と表示された。
逆にlistはイテレータを生成することのできるデータ型なので、print(list)とすれば、データが出力される。

以上のような解釈なりましたが、どこかおかしい部分があれば指摘していただけるととても勉強になります。

最後に、print(range(1, 11))とすると、自分の考えでは、1から10の値が出力されると思ったのですが、ただ文字列かのように、range(1, 11)と出力されています。今回の質問の主題とはすこし離れてしまいますが、この点も疑問なので、ご教授いただけると幸いです。

mac 10.14.1

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

checkベストアンサー

+3

<enumerate object at 0x102376318>となぜ表示されるのか

enumerate.__str__がそう返しているから。

listにしろrangeにしろ、文字列に変換されるとき(printの引数として渡したり、str関数(クラス)で明示的に文字列に変換したりといった場合)はまず__str__メソッドが呼ばれます。<enumerate object at 0x102376318>はその返り値です。

もう少し突っ込んだことを言えば、enumerate.__str__そのものは実は実装されておらず、すべての基底であるobjectクラスで実装されたobject.__str__が実行された結果、その表示になっています。

>>> enumerate.__str__
<slot wrapper '__str__' of 'object' objects>
>>> object.__str__
<slot wrapper '__str__' of 'object' objects>
>>> enumerate.__str__ is object.__str__
True

この辺を読んでください。
4. 組み込み型 — Python 3.6.5 ドキュメント
2. 組み込み関数 — Python 3.6.5 ドキュメント

イテレータかどうか

nextすればすぐにわかる。というかnextできるものがイテレータです。

>>> e = enumerate([0,1,2])
>>> next(e)  # イテレータ
(0, 0)
>>> m = map(lambda x:x, [0,1,2])
>>> next(m)  # イテレータ
0
>>> r = range(3)
>>> next(r)  # じゃない
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'range' object is not an iterator
>>> l = [0,1,2]
>>> next(l)  # じゃない
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'list' object is not an iterator

iterに渡せば返り値がイテレータになるものがイテラブルです。

どれくらい振る舞いが違うかというと、これくらい違いがあるのでけっこう注意が要ると思います。

>>> e = enumerate([0,1,2])
>>> list(e)
[(0, 0), (1, 1), (2, 2)]
>>> list(e)  # 2回目はイテレータを使い果たしているので空リストになる
[]
>>> r = range(3)
>>> list(r)
[0, 1, 2]
>>> list(r)  # あえて言えば内部的にlist(iter(r))のように取り扱われ、毎回イテラブル→イテレータ変換されるので空リストにならない
[0, 1, 2]

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/02/11 22:15

    回答ありがとうございます。出先でパソコンがなかったので返信遅れてしまいました。
    イテレータかどうかを調べる方法についても再認識できました。
    また、イテラブルの振る舞いについても違いがあることが明確なりました。
    とても勉強になりました。ありがとうございました。

    キャンセル

+2

非常に深い考察だと思います。
間違っている点があるとすれば、次の一文でしょうか。

listやrangeはイテレータが生成可能な関数らしいです。

listはイテレータを返しません。
実際にリストオブジェクトをnext関数に放り込むと例外が発生することが確認できます。

>>> lst = [1, 2, 3]
>>> next(lst)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'list' object is not an iterator
>>>
>>> it = iter(lst)
>>> next(it)
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

追記:  改めて確かめると、rangeオブジェクトもイテレータじゃないですね。知らなかったです。


リストやタプルなど、反復可能な要素はイテラブルと呼ばれます。
イテラブルから実際に値を辿りたいときにイテレータが取得されます。

ややこしいのは、イテレータもイテラブルであることです。
任意のイテレータitは、iter(it)に対して自分自身を返します。(註)

註:
その気になればそのルールに反する独自のイテレータを作るのは可能です。
ただしやるべきでは無いです。

iterator

...略...
イテレータは、そのイテレータオブジェクト自体を返す __iter__() メソッドを実装しなければならないので、イテレータは他の iterable を受理するほとんどの場所で利用できます。

引用元: Pythonドキュメント » 用語集

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/02/11 22:12

    回答ありがとうございます。出先でパソコンがなかったので返信遅れてしまいました。
    イテレータもイテラブルであるということでさらに理解が深まりました。
    イテレータを返すか返さないかについてももう少し検討しようと思います。
    ありがとうございました。

    キャンセル

+2

ここに記載することは感覚的な解釈です。

enumerate(branches)は「中に何が入ってますか?」と問い合わせるまでenumerateオブジェクトなんです。print()type()は中身のことには関知しませんのでenumerateオブジェクト以外の何物にもなりません。一方でlist()やfor文、もしくはnext()みたいなものは、中に何が入ってますかと問い合わせるので、enumerateオブジェクトは一つずつタプルを返し、結果あなたが観測されているように振舞います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/02/11 22:17

    回答ありがとうございます。出先でパソコンがなかったので返信遅れてしまいました。
    とても内容を噛み砕いていただき、直感的な感覚で理解できました。
    ありがとうございました。

    キャンセル

+2

いくつか回答がついてますが納得いったでしょうか?

自分は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__メソッドの定義が次のようになっているからです。(本当の定義内容ではなく本件の説明のためのコードにしています)

class list:
    ...
    def __str__(self):
        s = ""
        iterator = iter(self)
        while True:
            try:
                s += ", " + repr(next(iterator))
            except:
                break
        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とは異なる定義になっている点にご注意を。

class MyEnumerate:  # 本来の定義を壊さないようにあえてクラス名(型名)を変えています。
    def __init__(self, iterable):
        self.iterator = iter(iterable)
        self.ordinal = 0

    def __next__(self):  # next(このオブジェクト)とするとこのメソッドが呼び出される
        value = (self.ordinal, next(self.iterator))
        self.ordinal += 1
        return value

    def __iter__(self):  # iter(このオブジェクト)とやるとこのメソッドが呼び出される
        return self

    # ここより上はenumerateインスタンスがiteratorとして振舞えるようにするための定義

    def __str__(self):  # 前述のlist.__str__と同じ内容です
        s = ""
        iterator = iter(self)
        while True:
            try:
                s += ", " + repr(next(iterator))
            except:
                break
        return "[" + s[2:] + "]"


for i, e in MyEnumerate(['a', 'b']):
    print('i={}, e={}'.format(i, e))

# =>
# i=0, e=a
# i=1, e=b

print(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の実装例を挙げてみます。若干ややこしいかもしれませんがよく見ると単純な実装であることがわかります。(なお本件の説明のため大幅に省略した内容ですし正確でない点があります。)

class MyRange:
    def __init__(self, start, stop, step):
        self.start, self.stop, self.step = start, stop, step

    def __iter__(self):
        return MyRangeIterator(self)

    def __str__(self):
        return "MyRange({}, {}, {})".format(self.start, self.stop, self.step)

class MyRangeIterator:
    def __init__(self, range):
        self.range = range
        self.value = range.start

    def __next__(self):
        if self.value < self.range.stop:
            self.value += self.range.step
            return self.value - self.range.step
        else:
            raise StopIteration()

    def __iter__(self):
        return self

for i in MyRange(1, 6, 2):
    print(i)
# =>
# 1
# 3
# 5
print(MyRange(1, 6, 2))       # => MyRange(1, 6, 2)
print(iter(MyRange(1, 6, 2))  # => <__main__.MyRangeIterator object at 0x6fffabac0>

回答を書いてみて「どうも充分わかりやすく説明できてない」という感じは否めないです。Iterable/Iteratorの説明をするのにクラスや型の知識を前提とした説明になってしまっているからです。そのあたりは「雰囲気をなんとなくつかんでいただけたら」という思いで書いたのですが、かえってわかりづらかったらすみません。

なお上記に書いたことは既に質問者さんご自身の頭の中にほとんどあるような気もします・・・

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/02/11 22:25

    回答ありがとうございます。
    出先でパソコンがなかったので返信遅れていました。
    とてもボリューミーな回答ありがとうございます。
    hayataka2049さんの回答はとても興味深かったですが、KSwordOfHasteさんの解説もとても勉強になりました。ただまだ理解が不十分なので、後日もう一度読んで理解を深めようと思います。その時に疑問点があれば質問いたしますのでよろしくお願いします。
    取り急ぎ、感謝の意を表すために返信いたしました。ありがとうございました。

    キャンセル

  • 2019/02/12 23:26

    時間をかけて精読してみたのですが、自分の中の曖昧な部分が浮き彫りになり、とても勉強になりました。
    かなり噛み砕くと、print()は__str__メソッドを呼び出しているだけで、enumerate()の__str__には自分が思っていたような定義がされていなかったからtupleの列挙は出力されなかったというですね。
    本文を読んでみて、1点だけ正しく解釈しているか自信のない部分があります。
    『printしただけなのに、MyEnumerateインスタンスの状態がかわってしまう』とおっしゃっていた部分が含まれる段落の内容についてです。"状態が変わる"というのが完全に掴めてないのですが、以下のように解釈しました。

    __str__メソッドの内容に状態を変化するようなコードが書かれていた場合。
    nextで異なる要素を返すという動作をしている状態にあったのに、途中でprint()を行い、__str__メソッドを呼び出すことで、インスタンスの状態が変化し、nextの動作に支障が生じる(例えば、今まで取り出した要素がリセットしてしまい、また要素を一から取り出してしまう)という問題が発生する。だから、__str__メソッドにはそういった自分自身の状態を変えるような定義をしないようにしている。

    という解釈を自分の中でしてみたのですが、どうでしょうか?わかりづらい説明で申し訳ないのですが、回答よろしくお願いします。

    キャンセル

  • 2019/02/12 23:30

    > 途中でprint()を行い、__str__メソッドを呼び出すことで、インスタンスの状態が変化し、nextの動作に支障が生じる

    はい、そのとおりです。なお

    > 例えば、今まで取り出した要素がリセットしてしまい、また要素を一から取り出してしまう

    こうなってくれていればまだ多少はいいのですが、実際の動作は「それ以上何も要素が取り出せなくなる」となります。

    キャンセル

  • 2019/02/13 13:24

    返信ありがとうございます。
    実際の動作は取り出せなくなってしますのですね、勉強になります。
    print()とはなんなのかというところから丁寧に説明していただきありがとうございました。
    とても勉強になりました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.36%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • Python 3.x

    8486questions

    Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

  • トップ
  • Python 3.xに関する質問
  • enumerate object at 0x102376318と表示されることから、enumerate()のtupleがどういう形式で戻り値となっているかを解釈したが、正しいか判断してほしい。