LoginSignup
1
1

More than 5 years have passed since last update.

Pythonの「有効期間が重ならない2つのオブジェクトは同じid()値を持つかもしれません。」の一例

Last updated at Posted at 2019-04-12

はじめに

掲題は、Pythonドキュメントに記載されている
id()の説明の一部ですが
最初、見た時にピンと来ませんでした。

有効期間とは?
持つかもしれない、とは?

Pythonを動かしていて
たまたま、それっぽい動きを見つけたので
備忘もかねて記録しておきます。

Pythonドキュメントの説明

まず日本語版です。

id(object)
オブジェクトの"識別値"を返します。
この値は整数で、このオブジェクトの有効期間中は
一意かつ定数であることが保証されています。
有効期間が重ならない2つのオブジェクトは同じid()値を持つかもしれません。

「有効期間」は英語版の該当する単語から類推できます。

Return the "identity" of an object.
This is an integer which is guaranteed to be unique and constant
for this object during its lifetime.
Two objects with non-overlapping lifetimes may have the same id() value.

lifetimesとあるので
一般的な「変数のライフサイクル」と考えてよさそうです。

たまたまやってた事

仮引数として可変長引数が定義されている自前の関数を呼びだし
呼び出された先でその引数のIDを調べていました。

コード

こんな感じです。
func関数は可変長引数を受け取り
その中身であるタプルとそのIDをprintするだけです。

test.py
def func(*args):
    # print id(args)
    print(f'{"id(args) ":-<20}' + "> " + repr(id(args)))
    # print args
    print(args)


if __name__ == '__main__':
    # aとbで呼び出し
    a, b = 0, 1
    func(a, b)
    print()

    # 5と6で呼び出し
    func(5, 6)
    print()

    # 文字列とリストで呼び出し
    func('abc', [[], [], []])
    print()

    # リストをアンパックして呼び出し
    c = [i for i in range(5)]
    func(*c)
    print()

    # リストをアンパックして呼び出し
    d = [i for i in 'abcde']
    func(*d)
    print()

    # 色々入っている集合をアンパックして呼び出し
    e = {2, 3, (1,), 1, 'hoge'}
    func(*e)
    print()

    # 色々入っているリストをアンパックして呼び出し
    f = [{}, [], {a: 1, b: 2}, 3, (1, 2)]
    func(*f)
    print()

結果

タプルのIDはまちまちになりました。

結果
id(args) -----------> 18147208
(0, 1)

id(args) -----------> 18147208
(5, 6)

id(args) -----------> 18147208
('abc', [[], [], []])

id(args) -----------> 17979408
(0, 1, 2, 3, 4)

id(args) -----------> 17979408
('a', 'b', 'c', 'd', 'e')

id(args) -----------> 17979408
(1, 2, 3, (1,), 'hoge')

id(args) -----------> 17979408
({}, [], {0: 1, 1: 2}, 3, (1, 2))

クラスの場合

クラスの場合は全部同じになりました。

test2.py
class Test:
    def func(self, *args):
        print(f'{"id(args) ":-<20}' + "> " + repr(id(args)))
        print(args)


if __name__ == '__main__':
    t = Test()
    t.func(1)
    print()

    t.func('aaa')
    print()

    t.func([i for i in 'aaaaa'])
    print()

    t.func([i for i in range(10)])
    print()

    t.func({})
    print()
結果
id(args) -----------> 57210192
(1,)

id(args) -----------> 57210192
('aaa',)

id(args) -----------> 57210192
(['a', 'a', 'a', 'a', 'a'],)

id(args) -----------> 57210192
([0, 1, 2, 3, 4, 5, 6, 7, 8, 9],)

id(args) -----------> 57210192
({},)

試しに

渡す実引数を1つにしてIDを確認してみます。

test3.py
def func(*args):
    print(f'{"*args ":-<20}' + "> " + repr(*args))
    print(f'{"id(*args) ":-<20}' + "> " + repr(id(*args)))
    print(f'{"args ":-<20}' + "> " + repr(args))
    print(f'{"id(args) ":-<20}' + "> " + repr(id(args)))


if __name__ == '__main__':
    a = 1
    print(f'{"a ":-<20}' + "> " + repr(a))
    print(f'{"id(a) ":-<20}' + "> " + repr(id(a)))
    print()
    func(a)
    print()

    b = 2
    print(f'{"b ":-<20}' + "> " + repr(b))
    print(f'{"id(b) ":-<20}' + "> " + repr(id(b)))
    print()
    func(b)
    print()

    c = 'hoge'
    print(f'{"c ":-<20}' + "> " + repr(c))
    print(f'{"id(c) ":-<20}' + "> " + repr(id(c)))
    print()
    func(c)
結果
a ------------------> 1
id(a) --------------> 1745410224

*args --------------> 1
id(*args) ----------> 1745410224
args ---------------> (1,)
id(args) -----------> 61142352

b ------------------> 2
id(b) --------------> 1745410240

*args --------------> 2
id(*args) ----------> 1745410240
args ---------------> (2,)
id(args) -----------> 61142352

c ------------------> 'hoge'
id(c) --------------> 61993280

*args --------------> 'hoge'
id(*args) ----------> 61993280
args ---------------> ('hoge',)
id(args) -----------> 61142352

それぞれの実引数のIDはバラバラに出力されていますが
パックされたタプルのIDはやっぱり同じです。

まとめ

funcにおけるargsタプルのライフサイクルは
func関数の呼び出しからfunc関数の処理が終わるまでという認識です。
それが有効期間という意味なのだと解釈しました。

また「持つかもしれません」については
上記、クラスのコード例では全てのIDが同じになりましたが
関数では全てのIDが同じになるとも限りませんでした

何故、このような挙動になるのかはよくわかっていませんが
恐らくは
ライフサイクル満了時には、オブジェクトも破棄されるので
 破棄されたオブジェクトのIDが使いまわされている
と推測します。

もしくはどこかにIDが退避されているとか…?(考えにくいですが)
ともあれ、やや疑問は残りますが、こういうものだと思う事にします。

結論としては、もし「id()による識別値を使いたい」場合は
一意かつ定数であることが保証されています」というメリットだけに囚われず
そのオブジェクトのスコープやライフサイクルなどから
「有効期間」を意識する事が大事だと思いました。

1
1
0

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
1
1