2012-06-04 5 views
13

これは現実のシナリオです:私はアプリケーションを作成していますが、特定の種類のファイルを表すクラスがあります(私の場合は写真ですが、その詳細は問題とは関係ありません)。 Photographクラスの各インスタンスは、写真のファイル名に対して一意である必要があります。Pythonでクラスのインスタンス化をどのようにメモすることができますか?

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

これは私にとってはメモを使う良い状況のようですが、そこには多くの例がありますが、この場合私は普通の関数をメモしているだけではなく、__init__()をメモする必要があります。これは問題があります。__init__()が呼び出されるまでにはすでに新しいインスタンスが作成されているので、すでに遅すぎます。

私の研究では、Pythonの__new__()メソッドが見つかりましたが、実際には簡単な例を書くことができましたが、実際のオブジェクトに使用しようとすると分解してしまいました。私が考えることができるのは、私の実際の世界のオブジェクトは、私が本当に制御できない他のオブジェクトのサブクラスなので、このアプローチにはいくつかの非互換性がありました)。これは私が持っていたものです:

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> 

それは完璧です:これは出力

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() 

!一意の2つのidに対して2つのインスタンスのみが作成され、Flub.instancesには2つのリストが明示されています。

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

はそれと戦うしばらくして、私は基本的にはちょうどあきらめ、get呼ばstaticmethodにすべての__new__()黒魔術を移動し、私はPhotograph.get(filename)を呼び出すことができますし、ファイル名がPhotograph.instancesにはすでになかった場合にのみPhotograph(filename)を呼ぶだろうな。

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

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

Here's my real-world code using the staticmethod getすべて一緒に表示する場合は、 __new__

+1

あなたが言ったことを削除する質問を編集しました。 – robru

答えて

11

あなたの質問について2点を見てみましょう。あなたがメモ化を使用することができますが、あなたはクラス、ない__init__方法を飾る必要がありmemoize

を使用して

。私たちはこの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) 

出力は以下のとおりです。とにかく

1 3072319660 
2 3072319692 
3 3072319724 
2 3072319692 
4 3072319756 

、私はオブジェクトを生成し、それをmemoizeする関数を作成することを好む:同じパラメータが返されたオブジェクトの同じidをもたらすことに注意してください。私にはクリーンなようだが、それはいくつかの無関係なペットpeeveことがあります

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

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

__new__を使用する:

あるいは、もちろん、あなたが__new__を上書きすることができます。数日前、私はan answer about the ins, outs and best practices of overriding __new__を投稿しました。基本的には*args, **kwargs__new__メソッドに渡します。

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

+0

ありがとうございます。メソッドの代わりにクラスに直接デコレータを置くことができるとは気付きませんでした。それが私が見逃していた重要な情報でした。文字列は数字のようなシングルトンではないので(なぜなら、 'id'は同じ文字列から別の文字列に変わっていません)、私の必要とするものではありません。最初の引数を直接キーとして使用します。 – robru

+0

@Robru確かに私のメモは私が例で使っているクイックコードなので、注意を払う必要はありません:) – brandizzi

+0

もちろん、私の特定のクラス構成で作業するメモ作成デコレータを1時間虚報した後、このソリューションは実際には動作しません。なぜなら、ロードされたすべてのインスタンスに対して操作を実行するために 'ClassName.instances' dictを反復する多数のメソッドと関数があります。この特定のメモ処理テクニックは、単一のdictに変換する。結局私は '__new__'と一緒に行かなければならないようです。 – robru

2

パラメータもそう、__init__に渡されます:あなたはここで__init__

でそれを使用しない場合でも、そこにflubid引数を受け入れる必要が

def __init__(self, flubid): 
    ... 

は関連性がありますコメントtypeobject.c in Python2.7.3

/* 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. 

*/ 
+0

あなたが言うことは理にかなっていますが、私の簡単な例は '__init__'をまったく定義しないとどのように動作しますか?渡された引数の数が間違っていると私にも間違いを与えてはいけませんか? – robru

+0

@Robru、私は 'typeobject.c'で与えられた説明で私の答えを更新しました –

+0

ああ、ok。ありがとう。 – robru

3

私が使用して終了ソリューションはこれです:

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でデコレータを使用しているときに、デコレータ自体がに置き換えられていることを理解する必要があります()。 。だから、例えば私はそれがbrandizziの例から(memoized機能を指し、Photographが実際にオリジナルの写真のクラスを参照していないので、私は実際にそれらにアクセスすることができませんでした、Photograph.instancesまたはCamera.generate_id()(staticmethod)にアクセスしようと思いますとき)。

これを回避するために、装飾クラスからすべての属性と静的メソッドを実際に取り出して、それを自分のものとして公開するデコレータクラスを作成する必要がありました。ほとんどの場合、サブクラスのようになりますが、デコレータクラスはあらかじめどのクラスをデコレートするかを知らないので、その事実の後に属性をコピーする必要があります。

最終的には、memoizeクラスのインスタンスは、インスタンス化しようとすると実際に呼び出すことを除いて、装飾した実際のクラスの周りにほぼ透明なラッパーになります。彼らは利用可能です。

関連する問題