2011-12-16 13 views
6

spamをいくつかのクラスSpamのインスタンスとし、spam.hamが組み込みタイプのオブジェクト、たとえばdictとします。 Spamdictのサブクラスではありませんが、そのインスタンスには同じAPIを標準のdict(つまり同じシグネチャを持つ同じメソッド)としたいと思いますが、フォームのbazillion定型句のメソッドを入力しないようにしたいと考えています:Pythonで__special_methods__の委任を自動化するには?

def apimethod(self, this, that): 
     return self.ham.apimethod(this, that) 

私は次のことを試してみました:

class Spam(object): 
    def __init__(self): 
     self.ham = dict() 

    def __getattr__(self, attr): 
     return getattr(self.ham, attr) 

...しかし、それは__setitem__よう特別なメソッド、について "通常の" keysitemsのような方法、ではなくのために働く、__getitem__、および__len__

>>> spam = Spam() 
>>> spam.keys() 
[] 
>>> spam['eggs'] = 42 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: 'Spam' object does not support item assignment 
>>> spam.ham['eggs'] = 42 
>>> foo.items() 
[('eggs', 42)] 
>>> spam['eggs'] 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: 'Spam' object is not subscritable 
>>> len(spam) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: 'Spam' object has no len() 


私が試した全ての特別な方法は、同様のエラーを生成しました。

(それらはデリゲートと呼ばれますように)どのように私は特別な方法の定義を自動化することができますか?

明確化:標準的なメソッド検索シーケンスを活用するソリューションを必ずしも探しているわけではありません。私の目標は、定型コードを最小限に抑えることです。

ありがとうございます!

+1

私はあなたには正当な理由があると確信していますが、 'Spam'を' dict'のサブクラスにしたくない理由を説明できますか? – zwol

+0

Urgh。 Second Zack:もしあなたが 'dict'のように見えたいなら' dict'から継承します。 – katrielalex

答えて

5

これは便利ではないかもしれないが、ここで私が思いついた解決策は以下のとおりです。

def _wrapper(func): 
    def _wrapped(self, *args, **kwargs): 
     return getattr(self.ham, func)(*args, **kwargs) 
    return _wrapped 

class DictMeta(type): 
    def __new__(cls, name, bases, dct): 
     default_attrs = dir(object) 
     for attr in dir(dict): 
      if attr not in default_attrs: 
       dct[attr] = _wrapper(attr) 
     return type.__new__(cls, name, bases, dct) 

class Spam(object): 
    __metaclass__ = DictMeta 
    def __init__(self): 
     self.ham = dict() 

は、あなたが探しているものをやっているようだ:

>>> spam = Spam() 
>>> spam['eggs'] = 42 
>>> spam.items() 
[('eggs', 42)] 
>>> len(spam) 
1 
>>> spam.ham 
{'eggs': 42} 

Python 3.xではclass Spam(object, metaclass=DictMeta)を使用し、の本文から__metaclass__行を削除してください。

0

__getattribute__は役立ちますが、その理由は、特殊な方法がない場合には、クラスで検索されているわからない:http://docs.python.org/reference/datamodel.html#special-method-lookup-for-new-style-classes、例えばとして__getattr____getattribute__自分自身のような特別な方法はどこか調べなければなりません。このような

プロキシはすべき__dict____class__振る舞いなどの、可能な方法の競合についての事はあなたのラッパーが任意のメソッドを持っているために発生した場合、および確認他の問題があるか、たとえば、慎重に考えず、私にはトラブルを求めているようです。

再:、

あなただけ含まれているメンバーの全体のインターフェイスを複製する場合はそれが継承が何のためにあるのかだとして、それは、私にはアンチパターンのように思える:対持っ-です。 2つのdictオブジェクトとの関係がある場合はどうなりますか?

has-a関係では、通常、便利なAPIを作るために別の名前でそれらをエクスポートする便利なメソッドを選択します。だからSpam.append(item)の代わりにSpam.addBot(bot)があります。あなたにもメタクラスを禁止しているソリューションを必要とする場合

1

これは...メタクラスの仕事のようです。

def make_method(p, m): 
    def method(self, *a, **k): 
     return getattr(getattr(self, p),m)(*a, **k) 
    return method 

class Proxier(type): 
    def __new__(cls, name, bases, dict): 
     objs = dict.get('proxyobjs', []) 
     if objs: 
      old_init = dict.get('__init__', lambda self: None) 
      def new_init(self, *a, **k): 
       for (n,v) in objs.iteritems(): 
        setattr(self, n, v()) 
       old_init(self, *a, **k) 
      dict['__init__'] = new_init 
      meths = dict.get('proxymethods', {}) 
      for (proxyname, methnames) in meths.iteritems(): 
       for methname in methnames:     
        dict[methname] = make_method(proxyname, methname) 
     return super(Proxier, cls).__new__(cls, name, bases, dict) 


class Spam(object): 
    __metaclass__ = Proxier 
    proxyobjs = {'ham': dict, 
       'eggs': list, 
       } 
    proxymethods = {'ham': ('__setitem__', '__getitem__', '__delitem__'), 
        'eggs': ('__contains__', 'append') 
        } 

あなたが使用しているもののインターフェイスの部分を指定する必要があり

In [28]: s = Spam() 

In [29]: s[4] = 'hi' 

In [30]: s.append(3) 

In [31]: 3 in s 
Out[31]: True 

In [32]: 4 in s 
Out[32]: False 

In [33]: s[4] 
Out[33]: 'hi' 

注意(そうでない場合は、その理由だけで継承していません?)。したがって、__contains__list__getitem__dict__iter__はいずれもありません。 (appendではなく、extendまたは__delitem__を使用して、基になるリストを変更する唯一の方法です。)(Martianのように)私はこれがどれほど有用かどうかはわかりません。特別なメソッドの

1

属性へのアクセスは、基本的にこれらのメソッドは、クラスレベルで存在しなければなりません、通常の属性アクセスルールに従わないので、あなたは手動ですべてのこれらのメソッドを追加する必要があるか、あなたがクラスに追加することができますhttp://docs.python.org/reference/datamodel.html#special-method-lookup-for-new-style-classes

を読みますプログラムによって、そしてそれを行うための最善の方法は、メタクラスを通してです。

redirecting __setitem__ 
keys = ['eggs'] 
redirecting __getitem__ 
yolk 

免責条項::IMOこれはあまりにも多くの魔法とする必要があります。また残りの部分は簡単に__getattr__

def redirect(methodname): 
    def _redirect(self, *args, **kwargs): 
     print "redirecting",methodname 
     method = getattr(self.ham, methodname) 
     return method(*args, **kwargs) 

    return _redirect 

class DictRedirect(object): 
    def __new__(cls, name, bases, attrs): 

     # re-create all special methods from dict 
     dict_attr_names = set(dir(dict)) 
     common_names = set(dir(cls)) 
     for methodname in dict_attr_names-common_names: 
      if not methodname.startswith('__'): 
       continue 
      attrs[methodname] = redirect(methodname) 
     return type(name, bases, attrs) 

class Spam(object): 
    __metaclass__ = DictRedirect 

    def __init__(self): 
     self.ham = dict() 

    def __getattr__(self, name): 
     return getattr(self.ham, name) 

spam = Spam() 
spam['eggs'] = 'yolk' 
print 'keys =',spam.keys() 
print spam['eggs'] 

出力スルーリダイレクトすることができるので、私はdictだけの特別な方法ですべてのメソッドを追加するわけではないことに注意してください楽しいこと以外は避けてください:)