2016-06-30 4 views
4

Ich habe eine Wrapper-Klasse ähnlich wie dieses (stark vereinfacht) Beispiel:zuweisen (statt definiert) eine __getitem__ magische Methode bricht Indizierung

class wrap(object): 
    def __init__(self): 
     self._data = range(10) 

    def __getitem__(self, key): 
     return self._data.__getitem__(key) 

ich es wie folgt verwenden:

w = wrap() 
print w[2] # yields "2" 

Ich dachte, dass ich einen Funktionsaufruf optimieren und loswerden könnte, indem ich zu diesem ändere:

Jedoch empfange ich a

TypeError: 'wrap' object does not support indexing

für die print w[2] Linie mit der letzteren Version.

Der direkte Aufruf der Methode, das heißt print w.__getitem__(2), arbeitet in beiden Fällen ...

Warum erlaubt es die Zuordnung Version nicht Indizierung?

+0

Pro-Tipp: Wenn Sie nicht wüssten, warum das funktioniert, sollten Sie nicht versuchen, es zu optimieren. Ihr Overhead ist wahrscheinlich nicht so signifikant, wie Sie denken. – MisterMiyagi

+1

Aber wenn ich es nicht versucht hätte, hätte ich nicht gelernt wie es funktioniert, was mir nicht erlaubt hätte es zu tun. OTOH, wenn ich gewusst hätte, dass es kaputt ist, hätte ich es nicht ausprobiert ... Außerdem weißt du wahrscheinlich nicht, wie wichtig es für mich war. – NichtJens

+0

Beachten Sie, dass Sie das * tatsächlich lösen können, indem Sie für jeden Typ eine neue Klasse erstellen. Grundsätzlich benötigen Sie dafür eine Factory-Funktion. Dies kann transparent über '__new__' realisiert werden. Es ist jedoch etwas schwarze Magie ... Natürlich kann ich nur spekulieren, wie wichtig du denkst, dass es so ist; Als Referenz können Sie ungefähr 0,1 usec pro Anruf sichern. – MisterMiyagi

Antwort

4

Spezielle Methoden (im Wesentlichen alles mit zwei Unterstrichen an jedem Ende) müssen in der Klasse definiert werden. Die internal lookup procedure für spezielle Methoden überspringt das Instanz-Diktat vollständig. Unter anderem ist dies so, wenn Sie

class Foo(object): 
    def __repr__(self): 
     return 'Foo()' 

tun die __repr__ Methode, die Sie nur für Fälle von Foo verwendet definiert ist, und nicht für repr(Foo).

+0

Ah! Aha.Können Sie einen Link zu der Dokumentation dafür geben? Und, ich sehe nicht wirklich die Verbindung zwischen Ihrem ersten Absatz (der meine Frage beantwortet) und Ihrem Beispiel ... – NichtJens

+0

@NichtJens: Wenn spezielle Methode Lookup auf die Instanz dict schaute, würde 'repr (Foo)' aufheben die Methode 'Foo .__ repr__', die Sie definiert haben und die Sie verwenden. Das ist nicht das was du willst, denn diese Methode soll nur Instanzen von 'Foo' behandeln, nicht' Foo' selbst. – user2357112

+0

@NichtJens: [Hier ist] (https://docs.python.org/3/reference/datamodel.html#special-lookup) die Dokumentation, die ein ähnliches Beispiel verwendet. – user2357112

1

Sie können dies tatsächlich lösen, indem Sie für jeden Typ eine neue Klasse erstellen. Wenn Sie möchten, dass dies transparent funktioniert, ist __new__ der richtige Ort dafür.

import weakref 


class BigWrap(object): 
    def __new__(cls, wrapped): 
     wrapped_type = type(wrapped) 
     print('Wrapping %s (%s)' % (wrapped, wrapped_type)) 
     # creates a new class, aka a new type 
     wrapper_class = type( # new_class = type(class name, base classes, class dict) 
      '%s_%s_%d' % (cls.__name__, wrapped_type.__name__, id(wrapped)), # dynamic class name 
      (
       cls, # inherit from wrap to have all new methods 
       wrapped_type, # inherit from wrap_type to have all its old methods 
      ), 
      { 
       '__getitem__': wrapped.__getitem__, # overwrite __getitem__ based on wrapped *instance* 
       '__new__': wrapped_type.__new__, # need to use wrapped_type.__new__ as cls.__new__ is this function 
      }) 
     cls._wrappers[wrapped_type] = wrapper_class # store wrapper for repeated use 
     return cls._wrappers[wrapped_type](wrapped) 

    # self is already an instance of wrap_<type(wrapped)> 
    def __init__(self, wrapped): 
     self.__wrapped__ = wrapped 

Initiale "Lösung":

import weakref 
class wrap(object): 
    _wrappers = weakref.WeakValueDictionary() # cache wrapper classes so we don't recreate them 

    def __new__(cls, wrapped): 
    wrapped_type = type(wrapped) 
    print('Wrapping %s (%s)' % (wrapped, wrapped_type)) 
    try: 
     return object.__new__(cls._wrappers[wrapped_type]) # need to use object.__new__ as cls.__new__ is this function 
    except KeyError: 
     print('Creating Wrapper %s (%s)' % (wrapped, wrapped_type)) 
    # creates a new class, aka a new type 
    wrapper_class = type( # class name, base classes, class dict 
     '%s_%s' % (cls.__name__, wrapped_type.__name__), # dynamic class name 
     (cls,), # inherit from wrap to have all its method 
     {'__getitem__': wrapped_type.__getitem__}) # overwrite __getitem__ based on wrapped class 
    cls._wrappers[wrapped_type] = wrapper_class # store wrapper for repeated use 
    return cls._wrappers[wrapped_type](wrapped) 

    # self is already an instance of wrap_<type(wrapped)> 
    def __init__(self, wrapped): 
    self._data = wrapped 

aber Vorsicht! Dies wird tun, was Sie wollen - verwenden Sie die umschlossene Klasse '__getitem__. Dies macht jedoch nicht immer Sinn! Zum Beispiel ist list.__getitem__ tatsächlich in CPythons CAPI integriert und nicht auf andere Typen anwendbar.

foo = wrap([1,2,3]) 
print(type(foo)) # __main__.wrap_list 
foo[2] 
--------------------------------------------------------------------------- 
TypeError         Traceback (most recent call last) 
<ipython-input-31-82791be7104b> in <module>() 
----> 1 foo[2] 

TypeError: descriptor '__getitem__' for 'list' objects doesn't apply to 'wrap_list' object 
+1

Ich sehe. Also, auch wenn wir die "special lookup" -Prozedur dazu bringen können, das "__getitem__" des Wrapped tatsächlich zu betrachten, funktioniert der Trick nicht konsistent. Das ist ein bisschen eine Schande ... Abgesehen davon, ich fühle, dass Ihr Beispiel als Dekorateur umgeschrieben werden könnte, indem Sie die Methoden, die eingepackt werden sollen, als Argument verwenden. Aber wegen der obigen Unstimmigkeiten ist es das wahrscheinlich nicht wert. – NichtJens

+0

@NichtJens Ja, wenigstens die Methode direkt zu benutzen scheint sich nicht zu lohnen. Derselbe Ansatz könnte verwendet werden, um die Vererbung des Wrappers dynamisch zu ändern - d. H. Sie zu einer tatsächlichen Unterklasse der umhüllten Klasse machen. – MisterMiyagi

Verwandte Themen