25

わかりました、これが実際のシナリオです: 私はアプリケーションを書いていて、特定の種類のファイルを表すクラスがあります (私の場合、これは写真ですが、その詳細は問題とは無関係です)。Photograph クラスの各インスタンスは、写真のファイル名に対して一意である必要があります。

問題は、ユーザーがアプリケーションにファイルをロードするように指示した場合、ファイルが既にロードされていることを識別し、同じファイル名で重複するインスタンスを作成するのではなく、そのファイル名の既存のインスタンスを使用できるようにする必要があることです。

私には、これはメモ化を使用するのに適した状況のように思えます。その例はたくさんありますが、この場合、通常の関数をメモ化するだけでなく、メモ化する必要があります__init__()。これは問題を引き起こし__init__()ます。新しいインスタンスが既に作成されているため、呼び出されるまでには手遅れだからです。

私の研究で、私は Python の__new__()メソッドを見つけ、実際に動作する簡単な例を書くことができましたが、実際のオブジェクトでそれを使用しようとするとバラバラになり、理由がわかりません (私ができる唯一のこと)私の現実世界のオブジェクトは、実際には制御できない他のオブジェクトのサブクラスであったため、このアプローチにはいくつかの非互換性があったと考えてください)。これは私が持っていたものです:

class Flub(object):
    instances = {}

    def __new__(cls, flubid):
        try:
            self = Flub.instances[flubid]
        except KeyError:
            self = Flub.instances[flubid] = super(Flub, cls).__new__(cls)
            print 'making a new one!'
            self.flubid = flubid
        print id(self)
        return self

    @staticmethod
    def destroy_all():
        for flub in Flub.instances.values():
            print 'killing', flub


a = Flub('foo')
b = Flub('foo')
c = Flub('bar')

print a
print b
print c
print a is b, b is c

Flub.destroy_all()

これを出力する:

making a new one!
139958663753808
139958663753808
making a new one!
139958663753872
<__main__.Flub object at 0x7f4aaa6fb050>
<__main__.Flub object at 0x7f4aaa6fb050>
<__main__.Flub object at 0x7f4aaa6fb090>
True False
killing <__main__.Flub object at 0x7f4aaa6fb050>
killing <__main__.Flub object at 0x7f4aaa6fb090>

パーフェクトだ!指定された 2 つの一意の ID に対して作成されたインスタンスは 2 つだけで、Flub.instances には明らかに 2 つのみがリストされています。

しかし、私が使用していたオブジェクトでこのアプローチをとろうとすると、__init__()2 ではなく 0 の引数しか取らなかったという、あらゆる種類の無意味なエラーが発生し__init__()ました。 . 完全に奇妙です。

しばらく格闘した後、私は基本的にすべての黒魔術をあきらめて、すべての黒魔術をと呼ば__new__()れる staticmethod に移動しました。getPhotograph.get(filename)Photograph(filename)Photograph.instances

ここで私がどこで間違ったのか誰か知っていますか? これを行うためのより良い方法はありますか?

それについての別の考え方は、シングルトンに似ているということです。ただし、グローバルなシングルトンではなく、ファイル名ごとにシングルトンである点が異なります。

すべてをまとめて見たい場合は、これが staticmethod get を使用した私の実際のコードです。

4

3 に答える 3

20

ご質問のポイントを 2 つ見てみましょう。

メモ化の使用

メモ化を使用できますが、メソッドではなくクラス__init__を装飾する必要があります。この memoizator があるとします:

def get_id_tuple(f, args, kwargs, mark=object()):
    """ 
    Some quick'n'dirty way to generate a unique key for an specific call.
    """
    l = [id(f)]
    for arg in args:
        l.append(id(arg))
    l.append(id(mark))
    for k, v in kwargs:
        l.append(k)
        l.append(id(v))
    return tuple(l)

_memoized = {}
def memoize(f):
    """ 
    Some basic memoizer
    """
    def memoized(*args, **kwargs):
        key = get_id_tuple(f, args, kwargs)
        if key not in _memoized:
            _memoized[key] = f(*args, **kwargs)
        return _memoized[key]
    return memoized

次に、クラスを装飾する必要があります。

@memoize
class Test(object):
    def __init__(self, somevalue):
        self.somevalue = somevalue

テストを見てみましょうか?

tests = [Test(1), Test(2), Test(3), Test(2), Test(4)]
for test in tests:
    print test.somevalue, id(test)

出力は以下のとおりです。同じパラメータは、返されるオブジェクトの同じ ID を生成することに注意してください。

1 3072319660
2 3072319692
3 3072319724
2 3072319692
4 3072319756

とにかく、オブジェクトを生成してメモ化する関数を作成したいと思います。私にはきれいに見えますが、それは無関係なペットの怒りかもしれません:

class Test(object):
    def __init__(self, somevalue):
        self.somevalue = somevalue

@memoize
def get_test_from_value(somevalue):
    return Test(somevalue)

使用__new__:

もちろん、オーバーライドすることもできます__new__。数日前、役立つオーバーライドのイン、アウト、およびベスト プラクティスに関する回答__new__を投稿しました。基本的に、常にメソッドに渡す*args, **kwargsように指示されています__new__

私は、オブジェクトを作成する関数をメモ化したり、オブジェクトを同じパラメーターに再作成しないようにする特定の関数を作成したりすることを好みます。もちろん、これは私の意見であり、ルールではありません。

于 2012-06-04T13:22:36.867 に答える
8

私が最終的に使用した解決策はこれです:

class memoize(object):
    def __init__(self, cls):
        self.cls = cls
        self.__dict__.update(cls.__dict__)

        # This bit allows staticmethods to work as you would expect.
        for attr, val in cls.__dict__.items():
            if type(val) is staticmethod:
                self.__dict__[attr] = val.__func__

    def __call__(self, *args):
        key = '//'.join(map(str, args))
        if key not in self.cls.instances:
            self.cls.instances[key] = self.cls(*args)
        return self.cls.instances[key]

そして、ではなく、これでクラス__init__を飾ります。brandizzi は重要な情報を提供してくれましたが、彼のサンプル デコレータは期待どおりに機能しませんでした。

この概念は非常に微妙であることがわかりましたが、基本的に Python でデコレーターを使用する場合、装飾されるもの (メソッドであろうとクラスであろうと) が実際にはデコレーター自体に置き換えられることを理解する必要があります。たとえば、Photograph.instancesor (静的メソッド) にアクセスしようとすると、元の Photograph クラスを実際には参照せず、関数(brandizzi の例から)を参照するCamera.generate_id()ため、実際にはアクセスできませんでした。Photographmemoized

これを回避するには、装飾されたクラスから実際にすべての属性と静的メソッドを取得し、それらを独自のものとして公開するデコレータ クラスを作成する必要がありました。サブクラスとほとんど同じですが、デコレータ クラスはどのクラスを装飾するかを前もって認識していないため、後で属性をコピーする必要があります。

最終的な結果として、クラスのインスタンスは、memoizeそれが装飾した実際のクラスのほぼ透過的なラッパーになります。例外として、インスタンス化しようとすると (ただし実際に呼び出すと)、利用可能な場合にキャッシュされたコピーが提供されます。

于 2012-06-06T08:06:28.370 に答える
2

__new__にも渡されるパラメータな__init__ので、次のようになります。

def __init__(self, flubid):
    ...

flubidで使用しなくても、そこで引数を受け入れる必要があります__init__

これは、 Python2.7.3のtypeobject.cから取得した関連コメントです。

/* You may wonder why object.__new__() only complains about arguments
   when object.__init__() is not overridden, and vice versa.

   Consider the use cases:

   1. When neither is overridden, we want to hear complaints about
      excess (i.e., any) arguments, since their presence could
      indicate there's a bug.

   2. When defining an Immutable type, we are likely to override only
      __new__(), since __init__() is called too late to initialize an
      Immutable object.  Since __new__() defines the signature for the
      type, it would be a pain to have to override __init__() just to
      stop it from complaining about excess arguments.

   3. When defining a Mutable type, we are likely to override only
      __init__().  So here the converse reasoning applies: we don't
      want to have to override __new__() just to stop it from
      complaining.

   4. When __init__() is overridden, and the subclass __init__() calls
      object.__init__(), the latter should complain about excess
      arguments; ditto for __new__().

   Use cases 2 and 3 make it unattractive to unconditionally check for
   excess arguments.  The best solution that addresses all four use
   cases is as follows: __init__() complains about excess arguments
   unless __new__() is overridden and __init__() is not overridden
   (IOW, if __init__() is overridden or __new__() is not overridden);
   symmetrically, __new__() complains about excess arguments unless
   __init__() is overridden and __new__() is not overridden
   (IOW, if __new__() is overridden or __init__() is not overridden).

   However, for backwards compatibility, this breaks too much code.
   Therefore, in 2.6, we'll *warn* about excess arguments when both
   methods are overridden; for all other cases we'll use the above
   rules.

*/
于 2012-06-04T10:33:03.210 に答える