Unterschied zwischen len () und .__ len __ ()?

100

Gibt es einen Unterschied zwischen Anrufen len([1,2,3])oder [1,2,3].__len__()?

Wenn es keinen sichtbaren Unterschied gibt, was wird hinter den Kulissen anders gemacht?

Kennzeichen
quelle

Antworten:

102

lenist eine Funktion zum Abrufen der Länge einer Sammlung. Es funktioniert durch Aufrufen der __len__Methode eines Objekts . __something__Attribute sind speziell und in der Regel mehr als man denkt und sollten im Allgemeinen nicht direkt aufgerufen werden.

Es wurde vor langer Zeit entschieden, dass die Länge eines Objekts eine Funktion und kein Methodencode sein sollte, da len(a)die Bedeutung für Anfänger klar, aber a.len()nicht so klar sein sollte. Als Python anfing __len__, existierte es nicht einmal und lenwar eine besondere Sache, die mit einigen Arten von Objekten funktionierte. Ob die Situation, die uns dies hinterlässt, absolut sinnvoll ist oder nicht, es ist hier, um zu bleiben.

Mike Graham
quelle
66

Es ist häufig der Fall, dass das "typische" Verhalten eines integrierten Operators oder Operators darin besteht, (mit unterschiedlicher und besserer Syntax) geeignete magische Methoden (solche mit Namen wie __whatever__) für die beteiligten Objekte aufzurufen. Oft hat der eingebaute oder Operator einen "Mehrwert" (er kann je nach den beteiligten Objekten unterschiedliche Pfade einschlagen) - im Fall von lenvs __len__ist es nur eine kleine Überprüfung der Integrität des eingebauten Operators, die in der fehlt magische Methode:

>>> class bah(object):
...   def __len__(self): return "an inch"
... 
>>> bah().__len__()
'an inch'
>>> len(bah())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object cannot be interpreted as an integer

Wenn Sie einen Aufruf des lenintegrierten Geräts sehen , sind Sie sicher, dass der Aufruf eine Ganzzahl, nicht negativ und weniger als 2 ** 31 zurückgegeben hat, wenn das Programm danach fortgesetzt wird, anstatt eine Ausnahme auszulösen Wenn Sie einen Anruf bei sehen xxx.__len__(), haben Sie keine Gewissheit (außer dass der Autor des Codes entweder nicht mit Python vertraut ist oder bis zu nichts Gutes tut ;-).

Andere integrierte Funktionen bieten einen noch höheren Mehrwert als einfache Überprüfungen und Lesbarkeit. Durch die einheitliche Gestaltung von Python für die Arbeit über Aufrufe von integrierten Funktionen und die Verwendung von Operatoren, niemals durch Aufrufe von magischen Methoden, wird den Programmierern die Last erspart, sich daran zu erinnern, welcher Fall welcher ist. (Manchmal tritt ein Fehler auf: Bis 2.5 mussten Sie aufrufen foo.next()- in 2.6, während dies aus Gründen der Abwärtskompatibilität immer noch funktioniert, sollten Sie aufrufen next(foo), und in 3.*wird die magische Methode __next__anstelle des "oops-ey" korrekt benannt next! - ).

Die allgemeine Regel sollte daher sein, eine magische Methode niemals direkt (aber immer indirekt über eine integrierte Methode) aufzurufen, es sei denn, Sie wissen genau, warum Sie dies tun müssen (z. B. wenn Sie eine solche Methode in einer Unterklasse überschreiben, wenn die Die Unterklasse muss sich auf die Oberklasse verschieben, die durch expliziten Aufruf der magischen Methode erfolgen muss.

Alex Martelli
quelle
Ich bin ein Python-Anfänger (nicht der Anfänger-Programmierer) und ich bin mir nicht sicher, ob "Wenn Sie einen Aufruf der integrierten Len sehen, sind Sie sicher, dass das Programm danach fortgesetzt wird, anstatt eine Ausnahme auszulösen". Ich habe es versucht: def len(x): return "I am a string." print(len(42)) print(len([1,2,3]))und es wurde I am stringzweimal gedruckt . Kannst du es mehr erklären?
Darek Nędza
4
@ DarekNędza Dies hat nichts mit dem oben Gesagten zu tun, bei dem es um eingebaute Len geht. Sie haben gerade Ihre len-Funktion definiert, die natürlich alles zurückgeben kann, was Sie wollen. OP sprach von Builtin Len, das eine __len__spezielle Methode (keine Funktion) für das betrachtete Objekt aufruft .
Veky
@Veky Wie kann ich sicher sein, dass ich eine integrierte Funktion aufrufe, lennicht eine andere Funktion (wie in meinem Beispiel), die zufällig denselben Namen hat - len. Es gibt keine Warnung wie "Sie definieren die integrierte Funktion len neu" oder ähnliches. Meiner Meinung nach kann ich nicht sicher sein, was Alex in seiner Antwort angegeben hat.
Darek Nędza
3
Alex sagte ausdrücklich, wenn Sie Builtin anrufen, dann sind Sie sicher ..._. Er sagte nichts darüber, sicher zu sein, dass Sie Builtin anrufen. Aber wenn Sie das wissen wollen, können Sie : len in vars(__builtins__).values().
Veky
1
Leider ist dies ein weiteres Beispiel für das Fehlen einer gemeinsamen Basisklasse für Objekte in Python. Syntaktische Kontextumschaltung war schon immer verrückt. In einigen Fällen ist es üblich, eine Unterstrichmethode zu verwenden, in anderen sollte man so etwas wie eine Funktion verwenden, um etwas zu tun, das vielen Objekten gemeinsam ist. Es ist auch seltsam, weil viele Objekte keine semantische Verwendung für len haben. Manchmal ist das Objektmodell eher C ++, Küche sinkend ..
uchuugaka
28

Sie können sich len () als ungefähr gleichwertig mit vorstellen

def len(x):
    return x.__len__()

Ein Vorteil ist, dass Sie damit Dinge wie schreiben können

somelist = [[1], [2, 3], [4, 5, 6]]
map(len, somelist) 

anstatt

map(list.__len__, somelist)

oder

map(operator.methodcaller('__len__'), somelist)

Es gibt jedoch ein etwas anderes Verhalten. Zum Beispiel bei Ints

>>> (1).__len__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute '__len__'
>>> len(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'int' has no len()
John La Rooy
quelle
2
Ich nehme an du meinst operator.methodcallerstatt operator.attrgetter.
Elazar
5

Sie können Pythond-Dokumente überprüfen :

>>> class Meta(type):
...    def __getattribute__(*args):
...       print "Metaclass getattribute invoked"
...       return type.__getattribute__(*args)
...
>>> class C(object):
...     __metaclass__ = Meta
...     def __len__(self):
...         return 10
...     def __getattribute__(*args):
...         print "Class getattribute invoked"
...         return object.__getattribute__(*args)
...
>>> c = C()
>>> c.__len__()                 # Explicit lookup via instance
Class getattribute invoked
10
>>> type(c).__len__(c)          # Explicit lookup via type
Metaclass getattribute invoked
10
>>> len(c)                      # Implicit lookup
10
Dmytro Ozarkiv
quelle