2011-12-16 18 views
6

Niech spam będzie instancją klasy Spam i przypuśćmy, że spam.ham jest obiektem typu wbudowanego, na przykład dict. Mimo że Spam nie jest podklasą dict, chciałbym, aby jego instancje miały ten sam interfejs API co zwykły dict (tj. Te same metody z tymi samymi sygnaturami), ale chcę uniknąć pisania na bazilionowych metodach formularza:Jak zautomatyzować delegowanie __special_methods__ w Pythonie?

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

próbowałem następujące:

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

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

... ale to działa na "zwykłych" metod, jak keys i items, ale nie dla metod specjalnych, jak __setitem__, __getitem__ i __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() 


Wszystkie specjalne metody próbowałem produkowane podobnych błędów.

Jak mogę zautomatyzować definicję metod specjalnych (tak aby uzyskać mowa delegata)?

Wyjaśnienie: Niekoniecznie szukam rozwiązań, które wykorzystują standardową sekwencję wyszukiwania metod. Moim celem jest zminimalizowanie kodu standardowego.

Dzięki!

+1

Jestem pewien, że masz dobry powód, ale czy mógłbyś wyjaśnić, dlaczego nie chcesz, aby 'Spam' był podklasą' dyktatu'? – zwol

+0

Urgh. Drugi Zack: jeśli chcesz wyglądać jak "dict", odziedzicz po "dyktorze". – katrielalex

Odpowiedz

5

Nie może to być pomocne, jeśli potrzebne jest rozwiązanie, które zakazuje metaclasses jak dobrze, ale tutaj jest rozwiązanie wymyśliłem:

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

wydaje się zrobić to, czego szukasz:

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

Jeśli na Pythonie 3.x użyjesz class Spam(object, metaclass=DictMeta) i usuniesz linię __metaclass__ z treści Spam.

0

Nie wiesz __getattribute__ pomoże, ale to dlatego, że są specjalne metody spojrzał w klasie nie na przykład: http://docs.python.org/reference/datamodel.html#special-method-lookup-for-new-style-classes, jak na przykład specjalne metody jak __getattr__ i __getattribute__ sami muszą być spojrzał gdzieś.

Proxy tak wydaje kłopoty dla mnie bez starannego myślenia, na przykład, jak powinien takie rzeczy __dict__ i __class__ i zachowują o możliwych konfliktów metoda jeśli wrapper zdarza się mieć żadnych metod, a na pewno są jeszcze inne problemy.

Re: is-a w porównaniu ma-a:

Jeśli tylko powielać cały interfejs zawartych członka, wydaje się, że anty-wzorzec dla mnie, jako to, co jest dla dziedziczenia. A co jeśli masz dwa ma - relacje do dwóch obiektów dyktujących?

W relacji ma-a zwykle wybiera się przydatne metody, często eksportując je pod różnymi nazwami, aby uzyskać sensowny interfejs API. Więc zamiast tego Spam.append(item) będziesz miał Spam.addBot(bot).

1

To wygląda na pracę dla ... metaclass!

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') 
        } 

Działa!

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' 

Pamiętaj, że musisz określić, jakich części interfejsu używasz (inaczej, dlaczego po prostu nie dziedziczysz?). Mamy więc __contains__ z list i __getitem__ z dict, a __iter__ z żadnego. (I tylko jeden sposób, aby zmutować listę bazową, używając append, ale nie lub __delitem__.) Więc (tak jak Marsjanin) nie jestem pewien, jak użyteczne będzie to.

1

Atrybut dostęp do specjalnych sposobów nie przestrzegać normalnych zasad dostępu atrybut, w zasadzie metody te muszą istnieć na poziomie klasy, przeczytaj http://docs.python.org/reference/datamodel.html#special-method-lookup-for-new-style-classes

Więc trzeba dodać wszystkie te metody ręcznie lub można je dodać do klasy programowo i najlepiej to zrobić poprzez metaklass. Należy również pamiętać, że nie jestem dodanie wszystkich metod w dict ale tylko metodami specjalnymi ponieważ reszta może być łatwo przekierowana thru __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'] 

wyjściowego:

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

Zastrzeżenie: IMO jest to zbyt dużo magii i powinna być unikałem z wyjątkiem zabawy :)

Powiązane problemy