4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Python: アンパック代入に対応したクラスを作る

Last updated at Posted at 2020-07-11

ふとアンパック代入ってどうやって実現されているんだろう、と試行錯誤してみた結果をメモしておきます。

__getitem__() を実装する

>>> class MyTuple:
...     def __getitem__(self, key):
...         print(key)
...         if key < 3:
...             return str(key)
...         else:
...             raise IndexError
...

アンパック代入には __getitem__() メソッドが利用されるようです 1
キーとしてインデックス番号が渡されます。

普通の代入の場合

__getitem__() は呼び出されません。

>>> x = MyTuple()
>>> x
<__main__.MyTuple object at 0x10a6bd850>

要素が少ないアンパック代入の場合

左辺の要素の数 + 1 回分、__getitem__() が呼び出されます。
いずれの呼び出しでも IndexError が発生しない場合、右辺の要素が多いとみなし、ValueError が発生します。

>>> x, y = MyTuple()
0
1
2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 2)

要素の数が一致するアンパック代入の場合

左辺の要素の数 + 1 回分、__getitem__() が呼び出されます。
最後の呼び出しで IndexError が発生した場合、左辺の要素と右辺の要素が一致しているとみなし、代入が成功します。

>>> x, y, z = MyTuple()
0
1
2
3
>>> x, y, z
('0', '1', '2')

要素が多いアンパック代入の場合

左辺の要素の数 + 1 回分、__getitem__() が呼び出されます。
呼び出しの途中で IndexError が発生した場合、右辺の要素が不足しているとみなし、代入が失敗します。

>>> v, x, y, z = MyTuple()
0
1
2
3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 4, got 3)

* 付きのアンパック代入の場合

左辺に * 付きの要素がある場合、IndexError が発生するまで __getitem__() が呼び出されます 2
その結果を左辺の変数にいい感じに代入します。

>>> a, *b = MyTuple()
0
1
2
3
>>> a, b
('0', ['1', '2'])

いい感じの例

>>> *a, b = MyTuple()
0
1
2
3
>>> a, b
(['0', '1'], '2')

ひとつぐらい要素が足りなくてもいい感じにしてくれます。

>>> *w, x, y, z = MyTuple()
0
1
2
3
>>> w, x, y, z
([], '0', '1', '2')
>>> w, *x, y, z = MyTuple()
0
1
2
3
>>> w, x, y, z
('0', [], '1', '2')
>>> w, x, *y, z = MyTuple()
0
1
2
3
>>> w, x, y, z
('0', '1', [], '2')
>>> w, x, y, *z = MyTuple()
0
1
2
3
>>> w, x, y, z
('0', '1', '2', [])

でも、ふたつ足りない場合はダメです。

>>> *v, w, x, y, z = MyTuple()
0
1
2
3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected at least 4, got 3)

__iter__() を実装する

__getitem__() の代わりに __iter__() を実装してもアンパック代入できます。

>>> class MyTuple2():
...     def __iter__(self):
...         return iter([1, 2, 3])
...

実装はかなり端折っていますが、3 つの要素を持つ配列のイテレータを返しています。
ちゃんと動きます。

>>> x, y, z = MyTuple2()
>>> x, y, z
(1, 2, 3)

まとめ

__getitem__() でも __iter__() のどちらでもよいということはつまり、iterable であればアンパック代入可能と言い換えることができますね。

反復可能オブジェクトの例には、(list, str, tuple といった) 全てのシーケンス型や、 dict や ファイルオブジェクト といった幾つかの非シーケンス型、 あるいは Sequence 意味論を実装した __iter__() メソッドか __getitem__() メソッドを持つ任意のクラスのインスタンスが含まれます。

  1. ドキュメントを探してみたのだけど、ぱっと出てこなかったので実験して調べました。

  2. いい感じ is PEP 3132 -- Extended Iterable Unpacking です。

4
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?