2013-10-22 2 views
10

Ich benutze Python 2.7.3.Verwendet das Entpacken von Argumenten Iteration oder Item-Getting?

Betrachten Sie eine Dummy-Klasse mit benutzerdefinierten (wenn auch schlecht) Iteration und item-bekommen Verhalten:

class FooList(list): 
    def __iter__(self): 
     return iter(self) 
    def next(self): 
     return 3 
    def __getitem__(self, idx): 
     return 3 

ein Exempel statuieren und das seltsame Verhalten sehen:

>>> zz = FooList([1,2,3]) 

>>> [x for x in zz] 
# Hangs because of the self-reference in `__iter__`. 

>>> zz[0] 
3 

>>> zz[1] 
3 

Aber jetzt wollen wir eine machen Funktion und führen Sie dann auspacken Argument auf zz:

def add3(a, b, c): 
    return a + b + c 

>>> add3(*zz) 
6 
# I expected either 9 or for the interpreter to hang like the comprehension! 

So Argument un packing erhält die Item-Daten irgendwie von zz, aber nicht, indem es entweder über das Objekt mit seinem implementierten Iterator iteriert und auch nicht, indem er einen Iterator des armen Mannes ausführt und __getitem__ für so viele Gegenstände aufruft, wie das Objekt hat.

Also die Frage ist: Wie erhält die Syntax add3(*zz) die Daten Mitglieder von zz wenn nicht durch diese Methoden? Fehle ich gerade ein anderes allgemeines Muster, um Datenmitglieder von einem solchen Typ zu bekommen?

Mein Ziel ist es, zu sehen, ob ich eine Klasse schreiben könnte, die Iteration oder Item-Getting so implementiert, dass sie ändert, was die Argument-Entpackungssyntax für diese Klasse bedeutet. Nachdem ich das obige Beispiel ausprobiert habe, frage ich mich jetzt, wie das Entpacken von Argumenten bei den zugrunde liegenden Daten ankommt und ob der Programmierer dieses Verhalten beeinflussen kann. Google gab dafür nur eine Menge Ergebnisse zurück, die die grundlegende Verwendung der Syntax *args erklären.

Ich habe keinen Anwendungsfall dafür, und ich behaupte nicht, dass es eine gute Idee ist. Ich will nur sehen, wie es aus Neugierde gemacht wird.

Added

Da die eingebauten Typen sind speziell behandelt, hier ist ein Beispiel mit object wo ich mein eigenes bekommen und setzen Verhalten nur eine Liste Objekt pflegen und implementieren Liste zu emulieren.

class FooList(object): 
    def __init__(self, lst): 
     self.lst = lst 
    def __iter__(self): raise ValueError 
    def next(self): return 3 
    def __getitem__(self, idx): return self.lst.__getitem__(idx) 
    def __setitem__(self, idx, itm): self.lst.__setitem__(idx, itm) 

In diesem Fall

In [234]: zz = FooList([1,2,3]) 

In [235]: [x for x in zz] 
--------------------------------------------------------------------------- 
ValueError        Traceback (most recent call last) 
<ipython-input-235-ad3bb7659c84> in <module>() 
----> 1 [x for x in zz] 

<ipython-input-233-dc9284300db1> in __iter__(self) 
     2  def __init__(self, lst): 
     3   self.lst = lst 
----> 4  def __iter__(self): raise ValueError 
     5  def next(self): return 3 
     6  def __getitem__(self, idx): return self.lst.__getitem__(idx) 

ValueError: 

In [236]: add_3(*zz) 
--------------------------------------------------------------------------- 
ValueError        Traceback (most recent call last) 
<ipython-input-236-f9bbfdc2de5c> in <module>() 
----> 1 add_3(*zz) 

<ipython-input-233-dc9284300db1> in __iter__(self) 
     2  def __init__(self, lst): 
     3   self.lst = lst 
----> 4  def __iter__(self): raise ValueError 
     5  def next(self): return 3 
     6  def __getitem__(self, idx): return self.lst.__getitem__(idx) 

ValueError: 

Aber statt, wenn ich Iteration stoppt sicherzustellen, und gibt immer 3, kann ich bekommen, was ich mit im ersten Fall zu spielen, um geschossen wurde:

class FooList(object): 
    def __init__(self, lst): 
     self.lst = lst 
     self.iter_loc = -1 
    def __iter__(self): return self 
    def next(self): 
     if self.iter_loc < len(self.lst)-1: 
      self.iter_loc += 1 
      return 3 
     else: 
      self.iter_loc = -1 
      raise StopIteration 
    def __getitem__(self, idx): return self.lst.__getitem__(idx) 
    def __setitem__(self, idx, itm): self.lst.__setitem__(idx, itm) 

Dann sehe ich das, was ist das, was ich ursprünglich erwartet:

In [247]: zz = FooList([1,2,3]) 

In [248]: ix = iter(zz) 

In [249]: ix.next() 
Out[249]: 3 

In [250]: ix.next() 
Out[250]: 3 

In [251]: ix.next() 
Out[251]: 3 

In [252]: ix.next() 
--------------------------------------------------------------------------- 
StopIteration        Traceback (most recent call last) 
<ipython-input-252-29d4ae900c28> in <module>() 
----> 1 ix.next() 

<ipython-input-246-5479fdc9217b> in next(self) 
    10   else: 
    11    self.iter_loc = -1 
---> 12    raise StopIteration 
    13  def __getitem__(self, idx): return self.lst.__getitem__(idx) 
    14  def __setitem__(self, idx, itm): self.lst.__setitem__(idx, itm) 

StopIteration: 

In [253]: ix = iter(zz) 

In [254]: ix.next() 
Out[254]: 3 

In [255]: ix.next() 
Out[255]: 3 

In [256]: ix.next() 
Out[256]: 3 

In [257]: ix.next() 
--------------------------------------------------------------------------- 
StopIteration        Traceback (most recent call last) 
<ipython-input-257-29d4ae900c28> in <module>() 
----> 1 ix.next() 

<ipython-input-246-5479fdc9217b> in next(self) 
    10   else: 
    11    self.iter_loc = -1 
---> 12    raise StopIteration 
    13  def __getitem__(self, idx): return self.lst.__getitem__(idx) 
    14  def __setitem__(self, idx, itm): self.lst.__setitem__(idx, itm) 

StopIteration: 

In [258]: add_3(*zz) 
Out[258]: 9 

In [259]: zz[0] 
Out[259]: 1 

In [260]: zz[1] 
Out[260]: 2 

In [261]: zz[2] 
Out[261]: 3 

In [262]: [x for x in zz] 
Out[262]: [3, 3, 3] 

Zusammenfassung

  1. Die Syntax *args nur auf Iteration beruht. Für integrierte Typen geschieht dies auf eine Weise, die in Klassen, die vom integrierten Typ erben, nicht direkt außer Kraft gesetzt werden kann.

  2. Diese beiden sind funktional äquivalent:

    foo(*[x for x in args])

    foo(*args)

  3. Diese sind auch für die Finite-Daten-Strukturen nicht gleichwertig.

    foo(*args)

    foo(*[args[i] for i in range(len(args))])

+1

Ich glaube, Sie werden interessantere Informationen finden, wenn Sie 'FooList' von' Objekt' anstatt von 'Liste' ableiten. –

+2

Dies hängt nicht wegen unendlicher Iteration; Es hängt, weil '__iter __ (self)' sich indirekt rekursiv aufruft, also nimmt es eine unendliche Rekursion zu sogar _get_ einem Iterator. Sie können das 'next' vollständig entfernen und das gleiche Verhalten bekommen ... – abarnert

+0

Ich habe auch eine separate' FooListIter' Klasse erstellt und hatte '__iter__' eine Instanz davon zurückgeben, mit dem gleichen' next' Verhalten wie oben, und es macht dasselbe . Aber danke für den Punkt, an dem sich 'Iter' hier nennt. – ely

Antwort

9

Sie haben von einem Python irritierend Warzen gebissen worden: eingebauten Typen und Subklassen von ihnen behandelt werden magisch an einigen Stellen.

Seit Ihrem Typ Unterklassen von list, Python greift magisch in seine Interna, um es zu entpacken. Es verwendet die echte Iterator-API überhaupt nicht. Wenn Sie print Anweisungen in Ihrem next und __getitem__ einfügen, werden Sie sehen, dass keiner aufgerufen wird. Dieses Verhalten kann nicht überschrieben werden. Stattdessen müssten Sie Ihre eigene Klasse schreiben, die die eingebauten Typen neu implementiert. Sie könnten versuchen, UserList zu verwenden; Ich habe nicht überprüft, ob das funktioniert.

Die Antwort auf Ihre Frage ist, dass das Entpacken des Arguments Iteration verwendet. Die Iteration selbst kann jedoch __getitem__ verwenden, wenn keine explizite __iter__ definiert ist. Sie können keine Klasse erstellen, die das Verhalten beim Entpacken von Argumenten definiert, das sich vom normalen Iterationsverhalten unterscheidet.

Das Iterator-Protokoll (im Wesentlichen "Wie funktioniert __iter__ funktioniert") sollte nicht für Typen gelten, die in Unterklassen eingebaute Typen wie list. Wenn Sie eine eingebaute Klasse ableiten, verhält sich Ihre Unterklasse in bestimmten Situationen magisch wie die zugrunde liegende eingebaute, ohne dass Sie Ihre eigenen magischen Methoden anwenden (wie __iter__). Wenn Sie das Verhalten vollständig und zuverlässig anpassen möchten, können Sie keine Unterklassen von integrierten Typen ableiten (außer natürlich object).

+0

Also für 'list',' tuple' und 'dict ', Kann ich nicht unterbrechen, welche Interna aufgerufen werden, um die Datenelemente während des Entpackens der Argumente zu erhalten? – ely

+2

@EMS: Warum möchten Sie? Wenn Sie kein "Listen"/"Tupel"/"Diktat" magisches Verhalten wollen, erben Sie nicht von ihnen; Sie können einfach ein 'list' /' tuple'/'dict' Mitglied behalten und delegieren (genau dort, wo Sie wollen und nicht dort, wo Sie nicht wollen). – abarnert

+0

Ich habe nicht gesagt, dass ich wollte. – ely

Verwandte Themen