6
1

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 5 years have passed since last update.

BrainPadAdvent Calendar 2017

Day 18

【小ネタ】pythonで同じkeyを持つインスタンスは一つにしたい

Last updated at Posted at 2017-12-18

木系のアルゴリズムをpythonで実装しようとしていたら時間が無くなってしまったので小ネタ。

木系のアルゴリズムで、Nodeを共有したい場合ってありますよね。
下の図のオレンジ色のノードみたいに。

image.png

Nodeを拡張する場合に、拡張したいNodeと同じNodeが既に存在しているかどうかを調べるのも結構面倒なので、そこをうまくやれるといいですよね。

Nodeにkeyを指定しておいて、同じkeyがあったら既存のインスタンスを返す、といった実装をしようと思ったら、metaclassを使えばできる、という小ネタです。

# 同じkeyをもつノードは一つにしたい場合
class NodeType(type):
    _instances = {}
     
    def __call__(cls, *args, key, **kwargs):
        # stateが同じものがあれば、すでに作られたインスタンスを返し、なければ新規作成
        if key not in cls._instances:
            instance = super(NodeType, cls).__call__(key=key, *args, **kwargs)
            cls._instances[key] = instance
         
        return cls._instances[key]

class Node(metaclass=NodeType):
    def __init__(self, key, name):
        self.key = key
        self.name = name
        self.id = id(self)
    
    def __repr__(self):
        return 'id: {}, name {}, key: {}'.format(self.id, self.name, self.key)

node1 = Node(key=1, name='1'); print(node1)
node2 = Node(key=2, name='2'); print(node2)
node1_2 = Node(key=1, name='1_2'); print(node1_2)

上記のようにtypeを継承したクラスを作り、metaclassを使うことで、実現可能です。

id: 1850359362728, name 1, key: 1
id: 1850359363008, name 2, key: 2
id: 1850359362728, name 1, key: 1

作られたインスタンスは、keyによって管理されています。

NodeType._instances
{1: id: 1850359362728, name 1, key: 1, 2: id: 1850359363008, name 2, key: 2}

時間がなさ過ぎたので以上。。。

この方法だと、NodeType._instancesにも参照が生じてしまうので、
今後は、弱参照をうまくつかって、ガベージコレクションがうまく働く方法を検討したいです。

(2017.12.19追記)
上記の方法だと、

del node2
node2_2 = Node(key=2, name='2_2'); print(node2_2)

このように一度消して再作成したつもりでも、

id: 1850359363008, name 2, key: 2

となり、nameやidの情報を見てわかる通り、最初に作成したインスタンスが生き続けています。
NodeType._instances で参照が残っているので、当たり前と言えば当たり前かもしれませんが。

そこで、弱参照を使ってこの問題に対応することを考えます。
pythonでは、weakrefライブラリを使うことで弱参照を扱えます。

下記にコードは書きますが、最初の4行しか変えていません。

from weakref import WeakValueDictionary

# 同じkeyをもつノードは一つにしたい場合
class NodeType(type):
    _instances = WeakValueDictionary()
     
    def __call__(cls, *args, key, **kwargs):
        # stateが同じものがあれば、すでに作られたインスタンスを返し、なければ新規作成
        if key not in cls._instances:
            instance = super(NodeType, cls).__call__(key=key, *args, **kwargs)
            cls._instances[key] = instance
         
        return cls._instances[key]

class Node(metaclass=NodeType):
    def __init__(self, key, name):
        self.key = key
        self.name = name
        self.id = id(self)
    
    def __repr__(self):
        return 'id: {}, name {}, key: {}'.format(self.id, self.name, self.key)

node1 = Node(key=1, name='1'); print(node1)
node2 = Node(key=2, name='2'); print(node2)
node1_2 = Node(key=1, name='1_2'); print(node1_2)

NoteTypeクラスの中で、_instancesを辞書型の代わりに
WeakValueDictionaryを使っています。

id: 1945971777040, name 1, key: 1
id: 1945971777432, name 2, key: 2
id: 1945971777040, name 1, key: 1

今度は、一度消して再作成してみると、

del node2
node2_2 = Node(key=2, name='2_2'); print(node2_2)
id: 1945971719192, name 2_2, key: 2

となり、インスタンスが新しく作られていることが確認できます。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?