7

オブジェクトの多くのインスタンスをインスタンス化する問題に取り組んでいます。ほとんどの場合、インスタンス化されたオブジェクトは同一です。メモリのオーバーヘッドを減らすために、すべての同一オブジェクトが同じアドレスを指すようにしたいと考えています。ただし、オブジェクトを変更するときは、新しいインスタンスを作成する必要があります。つまり、基本的にコピー オン ライト動作です。Pythonでこれを達成する最良の方法は何ですか?

Flyweight パターンが近づいています。例 ( http://codesnipers.com/?q=python-flyweightsから):

import weakref

class Card(object):
    _CardPool = weakref.WeakValueDictionary()
    def __new__(cls, value, suit):
        obj = Card._CardPool.get(value + suit, None)
        if not obj:
            obj = object.__new__(cls)
            Card._CardPool[value + suit] = obj
            obj.value, obj.suit = value, suit
        return obj

これは次のように動作します。

>>> c1 = Card('10', 'd')
>>> c2 = Card('10', 'd')
>>> id(c1) == id(c2)
True
>>> c2.suit = 's'
>>> c1.suit
's'
>>> id(c1) == id(c2)
True

望ましい動作は次のとおりです。

>>> c1 = Card('10', 'd')
>>> c2 = Card('10', 'd')
>>> id(c1) == id(c2)
True
>>> c2.suit = 's'
>>> c1.suit
'd'
>>> id(c1) == id(c2)
False

更新: 私は Flyweight パターンに出会いました。しかし、私は他のアプローチにもオープンです。

4

3 に答える 3

6

同一である必要がありますid(c1)==id(c2)か、それとも、重複したオブジェクトの作成を避けることが本当の目的である単なるデモンストレーションですか?

1 つのアプローチは、各オブジェクトを個別にすることですが、上記のように「実際の」オブジェクトへの内部参照を保持します。次に、任意の__setattr__呼び出しで、内部参照を変更します。

私はこれまでに何かをしたことがありません__setattr__が、次のようになると思います。

class MyObj:
    def __init__(self, value, suit):
        self._internal = Card(value, suit)

    def __setattr__(self, name, new_value):
        if name == 'suit':
            self._internal = Card(value, new_value)
        else:
            self._internal = Card(new_value, suit)

同様に、 を介して属性を公開しますgetattr

複製されたオブジェクトはまだたくさんありますが、それらの背後にある「実際の」バッキング オブジェクトのコピーは 1 つだけです。したがって、これは各オブジェクトが巨大な場合に役立ちますが、軽量の場合は役に立ちませんが、何百万ものオブジェクトがあります。

于 2012-09-10T21:18:57.653 に答える
3

不可能。

id(c1) == id(c2)

c1とはc2まったく同じオブジェクトへの参照です。そう

c2.suit = 's'と言っているのと全く同じc1.suit = 's'です。

Python には 2 つを区別する方法がありません (以前の呼び出しフレームのイントロスペクションを許可しない限り、ダーティ ハックにつながります)。

2 つの割り当てが同一であるため、名前が別のオブジェクトを参照するc2.suit = 's'原因となることを Python が認識する方法はありません。 c2


汚いハックがどのように見えるかを理解するために、

import traceback
import re
import sys
import weakref

class Card(object):
    _CardPool = weakref.WeakValueDictionary()
    def __new__(cls, value, suit):
        obj = Card._CardPool.get(value + suit, None)
        if not obj:
            obj = object.__new__(cls)
            Card._CardPool[value + suit] = obj
            obj._value, obj._suit = value, suit
        return obj
    @property
    def suit(self):
        return self._suit
    @suit.setter
    def suit(self, suit):
        filename,line_number,function_name,text=traceback.extract_stack()[-2]
        name = text[:text.find('.suit')]
        setattr(sys.modules['__main__'], name, Card(self._value, suit))

c1 = Card('10', 'd')
c2 = Card('10', 'd')
assert id(c1) == id(c2)

c2.suit = 's'
print(c1.suit)
# 'd'

assert id(c1) != id(c2)

このトレースバックの使用は、CPython などのフレームを使用する Python の実装でのみ機能しますが、Jython や IronPython では機能しません。

もう一つの問題は、

name = text[:text.find('.suit')]

非常に壊れやすく、たとえば、割り当てが次のようになっていると、台無しになります

if True: c2.suit = 's'

また

c2.suit = (
    's')

また

setattr(c2, 'suit', 's')

さらに別の問題は、名前c2がグローバルであると想定していることです。ローカル変数 (たとえば、関数内) または属性 ( obj.c2.suit = 's') にすることも簡単にできます。

割り当てが行われる可能性のあるすべての方法に対処する方法がわかりません。

これらのケースのいずれにおいても、ダーティ ハックは失敗します。

結論:使わない。:)

于 2012-09-10T21:14:24.470 に答える
0

これは、現在のフォームでは不可能です。名前(例では and )c1参照であり、同じオブジェクトへの他のすべての参照は言うまでもなく、を使用して参照を単純に変更することはできません。c2__setattr__

これが可能な唯一の方法は、次のようなものです。

c1 = c1.changesuit("s")

Wherec1.changesuitは、(新しく作成された) オブジェクトへの参照を返します。ただし、これは、各オブジェクトが 1 つの名前だけで参照されている場合にのみ機能します。あるいは、そのようなものでいくつかの魔法を行うことができるかもしれませんlocals()が、しないでください.

于 2012-09-10T21:15:01.903 に答える