Differenza tra len () e .__ len __ ()?


Risposte:


102

lenè una funzione per ottenere la lunghezza di una raccolta. Funziona chiamando il __len__metodo di un oggetto . __something__gli attributi sono speciali e di solito più di quanto sembri, e generalmente non dovrebbero essere chiamati direttamente.

È stato deciso ad un certo punto molto tempo fa che ottenere la lunghezza di qualcosa dovrebbe essere una funzione e non un codice di metodo, ragionando che len(a)il significato di questo sarebbe chiaro ai principianti ma a.len()non lo sarebbe stato altrettanto chiaro. Quando Python è iniziato __len__non esisteva nemmeno ed lenera una cosa speciale che funzionava con alcuni tipi di oggetti. Che la situazione che questo ci lascia abbia o meno senso, è qui per restare.


66

Accade spesso che il comportamento "tipico" di un built-in o di un operatore sia di chiamare (con una sintassi diversa e migliore) metodi magici adeguati (quelli con nomi simili __whatever__) sugli oggetti coinvolti. Spesso il built-in o l'operatore ha un "valore aggiunto" (è in grado di prendere percorsi diversi a seconda degli oggetti coinvolti) - nel caso di lenvs __len__, è solo un po 'di controllo dell'integrità del built-in che manca al metodo magico:

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

Quando vedi una chiamata al lenbuilt-in, sei sicuro che, se il programma continua dopo, invece di sollevare un'eccezione, la chiamata ha restituito un numero intero, non negativo, e inferiore a 2 ** 31 - quando vedi una chiamata a xxx.__len__(), non hai certezza (tranne che l'autore del codice non ha familiarità con Python o non ha buone intenzioni ;-).

Altri built-in forniscono ancora più valore aggiunto oltre ai semplici controlli di integrità e leggibilità. Progettando in modo uniforme tutto Python in modo che funzioni tramite chiamate a builtin e uso di operatori, mai tramite chiamate a metodi magici, i programmatori sono risparmiati dall'onere di ricordare quale caso è quale. (A volte si verifica un errore: fino al 2.5, dovevi chiamare foo.next()- in 2.6, anche se funziona ancora per la retrocompatibilità, dovresti chiamare next(foo), e in 3.*, il metodo magico è chiamato correttamente __next__invece di "oops-ey" next! - ).

Quindi la regola generale dovrebbe essere quella di non chiamare mai un metodo magico direttamente (ma sempre indirettamente tramite un built-in) a meno che tu non sappia esattamente perché devi farlo (ad esempio, quando stai sovrascrivendo un tale metodo in una sottoclasse, se il la sottoclasse deve essere rimandata alla superclasse che deve essere eseguita tramite chiamata esplicita al metodo magico).


Sono un utente principiante di Python (non il programmatore principiante pensava) e non sono sicuro di "Quando vedi una chiamata al built-in len, sei sicuro che, se il programma continua dopo quello piuttosto che sollevare un'eccezione". Ho provato questo: def len(x): return "I am a string." print(len(42)) print(len([1,2,3]))ed è stampato I am stringdue volte. Puoi spiegarlo di più?
Darek Nędza

4
@ DarekNędza Questo non ha nulla a che fare con quanto sopra, che riguarda il builtin len. Hai appena definito la tua funzione len, che ovviamente può restituire quello che vuoi. OP ha parlato di builtin len, che chiama __len__un metodo speciale (non una funzione) sull'oggetto in esame.
Veky

@Veky Come posso essere sicuro di chiamare una funzione incorporata e lennon un'altra funzione (come nel mio esempio) che ha lo stesso nome - len. Non ci sono avvisi come "Stai ridefinendo la funzione incorporata len" o qualcosa del genere. A mio parere, non posso essere sicuro di ciò che ha affermato Alex nella sua risposta.
Darek Nędza

3
Alex ha detto esplicitamente che se stai chiamando builtin, allora sei sicuro ..._. Non ha detto nulla sull'essere sicuri di chiamare builtin. Ma se volete sapere che è possibile: len in vars(__builtins__).values().
Veky

1
Purtroppo, questo è un altro esempio della mancanza di una classe base comune per gli oggetti in Python. Il cambio di contesto sintattico è sempre stato pazzesco. In alcuni casi è un linguaggio comune usare un metodo di sottolineatura, in altri si dovrebbe usare qualcosa come una funzione per fare qualcosa di comune a molti oggetti. È anche strano perché molti oggetti non hanno un uso semantico di len. A volte il modello a oggetti è più simile a C ++, kitchen sinky ..
uchuugaka

28

Puoi pensare a len () come approssimativamente equivalente a

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

Un vantaggio è che ti permette di scrivere cose come

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

invece di

map(list.__len__, somelist)

o

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

Tuttavia, c'è un comportamento leggermente diverso. Ad esempio nel caso di int

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

2
Presumo tu intenda operator.methodcallerinvece di operator.attrgetter.
Elazar

5

Puoi controllare i documenti di Pythond :

>>> 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.