2017-01-04 3 views
26
class Foo: 
    def __getitem__(self, item): 
     print('getitem', item) 
     if item == 6: 
      raise IndexError 
     return item**2 
    def __len__(self): 
     print('len') 
     return 3 

class Bar: 
    def __iter__(self): 
     print('iter') 
     return iter([3, 5, 42, 69]) 
    def __len__(self): 
     print('len') 
     return 3 

Demo:Warum fragt die Liste nach __len__?

>>> list(Foo()) 
len 
getitem 0 
getitem 1 
getitem 2 
getitem 3 
getitem 4 
getitem 5 
getitem 6 
[0, 1, 4, 9, 16, 25] 
>>> list(Bar()) 
iter 
len 
[3, 5, 42, 69] 

Warum list Anruf __len__? Es scheint das Ergebnis nicht für etwas Offensichtliches zu verwenden. Eine for Schleife macht es nicht. Dies wird nirgendwo in der iterator protocol erwähnt, die nur über __iter__ und __next__ spricht.

Python reservieren Platz für die Liste im Voraus, oder so etwas kluges?

(CPython 3.6.0 auf Linux)

+2

Ja, es ist wahrscheinlich die Platzreservierung. Vielleicht kann jemand den kompilierten Code sehen und eine gute Antwort erstellen. –

Antwort

23

Siehe Rationale section from PEP 424 die __length_hint__ eingeführt und bietet einen Einblick auf die Motivation:

Die Möglichkeit, Listen über die zu erwartende Größe vorbelegt basiert, wie von __length_hint__ geschätzt, kann eine signifikante Optimierung sein. Es wurde beobachtet, dass CPython Code schneller ausführt als PyPy, nur weil diese Optimierung vorhanden ist.

Zusätzlich zu, dass überprüft die Dokumentation for object.__length_hint__ die Tatsache, dass dies eine reine Optimierungsfunktion ist:

Genannt operator.length_hint() zu implementieren. Sollte eine geschätzte Länge für das Objekt zurückgeben (die größer oder kleiner als die tatsächliche Länge sein kann). Die Länge muss eine Ganzzahl >= 0 sein. Diese Methode ist rein eine Optimierung und wird nie für die Richtigkeit erforderlich.

Also __length_hint__ ist hier, weil es einige nette Optimierungen ergeben kann.

PyObject_LengthHint, first tries to get a value from object.__len__ (if it is defined) und versucht dann zu sehen, ob object.__length_hint__ verfügbar ist. Wenn keines vorhanden ist, wird der Standardwert 8 für Listen zurückgegeben.

listextend, die von list_init genannt wird, wie Eli in seiner Antwort erwähnt, wurde nach diesem PEP modifiziert diese Optimierung für alles bieten, die entweder ein __len__ oder ein __length_hint__ definiert.

list ist nicht die einzige, die von diesem profitiert, natürlich, bytes objects do:

>>> bytes(Foo()) 
len 
getitem 0 
... 
b'\x00\x01\x04\t\x10\x19' 

so do bytearray objects but, only when you extend them:

>>> bytearray().extend(Foo()) 
len 
getitem 0 
... 

und tuple Objekte, die an intermediary sequence to füllen sich erstellen:

>>> tuple(Foo()) 
len 
getitem 0 
... 
(0, 1, 4, 9, 16, 25) 

Wenn jemand wandert, warum genau 'iter'vor'len' in der Klasse gedruckt wird Bar und nicht nach dem, wie mit Klasse geschieht Foo:

Dies, weil, wenn das Objekt in der Hand definiert ein __iter__ , wodurch die print('iter') läuft auch. Dasselbe passiert nicht, wenn es auf __getitem__ zurückgreift.

+2

Dies ist ein großartiger Fund für die Motivation! –

+1

@EliBendersky [git Schuld] (https://github.com/python/cpython/blame/master/Objects/listobject.c#L834) ist ein Wundertäter in diesen Fällen: -D –

28

list ist ein Listenobjekt Konstruktor, der für seinen Inhalt einen anfänglichen Scheibe Speicher zuteilen wird. Der Listenkonstruktor versucht, eine gute Größe für diesen ersten Speicherabschnitt herauszufinden, indem er den Längenhinweis oder die Länge eines beliebigen Objekts überprüft, das an den Konstruktor übergeben wird. Siehe den Anruf PyObject_LengthHint im Python source here. Dieser Ort wird aus der Liste Konstruktor aufgerufen - list_init

Wenn Ihr Objekt hat keine __len__ oder __length_hint__, das ist in Ordnung - ein default value of 8 verwendet wird; es kann nur aufgrund von Neuzuweisungen weniger effizient sein.

+2

Ist das ein CPython-Implementierungsdetail oder ein dokumentierter Teil von Python? Weil es eine unendliche Rekursion verursachen kann, wenn Sie nicht im voraus über diesen Anruf wissen. – wim

+4

"* es braucht eine Größe *" und "* ein Standardwert wird verwendet *" scheinen sich zu widersprechen. Anstelle von "es braucht eine Größe", wie wäre es "verwendet es die Größe, um Speicher vorab zuzuweisen." –

+0

@ Robᵩ: Ich wollte sagen, dass es etwas Größe braucht, um loszulegen. Nicht, dass es '__len__' braucht, nur eine Größe. Die Größe kann entweder von '__len__' oder von einem Standard sein. Fühlen Sie sich frei, vorzuschlagen und zu editieren und lassen Sie Redakteure entscheiden, was die klarere Formulierung ist - ich fühle mich nicht stark darüber –

Verwandte Themen