Perché il codice Python usa la funzione len () invece di un metodo length?


204

So che Python ha una len()funzione che viene utilizzata per determinare la dimensione di una stringa, ma mi chiedevo perché non fosse un metodo dell'oggetto stringa.

Aggiornare

Ok, mi sono reso conto di essermi sbagliato in modo imbarazzante. __len__()è in realtà un metodo di un oggetto stringa. Sembra strano vedere il codice orientato agli oggetti in Python usando la funzione len sugli oggetti stringa. Inoltre, è anche strano vedere __len__come il nome anziché solo len.

Risposte:


180

Le stringhe hanno un metodo di lunghezza: __len__()

Il protocollo in Python è quello di implementare questo metodo su oggetti che hanno una lunghezza e usano la len()funzione integrata, che lo chiama per te, in modo simile al modo in cui implementeresti __iter__()e utilizzeresti la iter()funzione integrata (o far chiamare il metodo dietro le scene per te) su oggetti iterabili.

Vedere Emulazione dei tipi di contenitore per ulteriori informazioni.

Ecco una buona lettura sull'argomento dei protocolli in Python: Python e il principio del minimo stupore


124
Mi stupisce di quanto sia idiota il motivo dell'uso len. Pensano che sia più facile forzare le persone a implementare .__len__che forzare le persone a implementare .len(). È la stessa cosa e uno sembra molto più pulito. Se la lingua avrà un OOP __len__, qual è il punto di partenza del mondolen(..)
alternativa

38
len, str, ecc. possono essere utilizzati con funzioni di ordine superiore come mappa, riduzione e filtro senza la necessità di definire una funzione o lambda solo per chiamare un metodo. Non tutto ruota attorno a OOP, nemmeno in Python.
Evicatos,

11
Inoltre, usando un protocollo, possono fornire modi alternativi di implementare le cose. Ad esempio, puoi creare un iterabile con __iter__o con solo __getitem__e iter(x)funzionerà in entrambi i modi. È possibile creare un oggetto utilizzabile in contesto bool con __bool__o __len__e bool(x)funzionerà in entrambi i modi. E così via. Penso che Armin lo spieghi abbastanza bene nel post collegato, ma anche se non lo facesse, chiamare Python idiota a causa di una spiegazione esterna di un ragazzo che è spesso pubblicamente in contrasto con gli sviluppatori principali non sarebbe esattamente giusto ...
abarnert

8
@Evicatos: +1 per "Non tutto ruota attorno a OOP", ma finirei con " soprattutto in Python", nemmeno . Python (a differenza di Java, Ruby o Smalltalk) non cerca di essere un linguaggio "puro OOP"; è esplicitamente progettato per essere un linguaggio "multi-paradigma". Le comprensioni dell'elenco non sono metodi su iterabili. zipè una funzione di livello superiore, non un zip_withmetodo. E così via. Solo perché tutto è un oggetto non significa che essere un oggetto è la cosa più importante di ogni cosa.
abarnert,

7
Quell'articolo è scritto così male che mi sento confuso e arrabbiato dopo aver provato a leggerlo. "In Python 2.x il tipo Tuple, ad esempio, non espone alcun metodo non speciale e tuttavia puoi usarlo per ricavarne una stringa:" Il tutto è una fusione di frasi quasi indecifrabili.
MirroredFate

93

La risposta di Jim a questa domanda può essere d'aiuto; Lo copio qui. Citando Guido van Rossum:

Prima di tutto, ho scelto len (x) su x.len () per motivi HCI (def __len __ () è arrivato molto più tardi). Esistono in realtà due motivi intrecciati, entrambi HCI:

(a) Per alcune operazioni, la notazione con prefisso è semplicemente migliore delle operazioni postfisso - prefisso (e infisso!) hanno una lunga tradizione in matematica a cui piacciono le notazioni in cui la grafica aiuta il matematico a pensare a un problema. Confronta la facilità con cui riscriviamo una formula come x * (a + b) in x a + x b alla goffaggine di fare la stessa cosa usando una notazione OO grezza.

(b) Quando leggo il codice che dice len (x) so che sta chiedendo la lunghezza di qualcosa. Questo mi dice due cose: il risultato è un numero intero e l'argomento è una specie di contenitore. Al contrario, quando leggo x.len (), devo già sapere che x è un tipo di contenitore che implementa un'interfaccia o eredita da una classe che ha un standard len (). Testimone della confusione che occasionalmente abbiamo quando una classe che non implementa una mappatura ha un metodo get () o keys () o qualcosa che non è un file ha un metodo write ().

Dicendo la stessa cosa in un altro modo, vedo 'len' come un'operazione integrata. Odierei perderlo. / ... /


37

C'è un lenmetodo:

>>> a = 'a string of some length'
>>> a.__len__()
23
>>> a.__len__
<method-wrapper '__len__' of str object at 0x02005650>

34

Python è un linguaggio di programmazione pragmatico, e le ragioni per len()essere una funzione e non un metodo di str, list, dictecc, sono pragmatici.

La len()funzione integrata si occupa direttamente dei tipi integrati: l'implementazione di CPython len()restituisce effettivamente il valore del ob_sizecampo nella PyVarObjectstruttura C che rappresenta qualsiasi oggetto incorporato di dimensioni variabili in memoria. Questo è molto più veloce rispetto alla chiamata di un metodo: non deve avvenire la ricerca degli attributi. Ottenere il numero di elementi in una collezione è un'operazione comune e deve lavorare in modo efficiente per tali tipi di base e diversi come str, list, array.arrayetc.

Tuttavia, per promuovere la coerenza, quando si applica len(o)a un tipo definito dall'utente, Python chiama o.__len__()come fallback. __len__, __abs__E tutti gli altri metodi speciali documentati nel modello di dati Python rendono facile per creare oggetti che si comportano come i built-in, permettendo le API espressivi e altamente coerenti noi chiamiamo "Pythonic".

Implementando metodi speciali i tuoi oggetti possono supportare l'iterazione, sovraccaricare gli operatori infix, gestire i contesti in withblocchi ecc. Puoi pensare al Modello Dati come un modo di usare il linguaggio Python stesso come un framework in cui gli oggetti che crei possono essere integrati senza problemi.

Un secondo motivo, supportato da citazioni di Guido van Rossum come questo , è che è più facile da leggere e scrivere len(s)che s.len().

La notazione len(s)è coerente con operatori unari con notazione prefisso, come abs(n). len()è usato molto più spesso di abs()e merita di essere facile da scrivere.

Ci può anche essere una ragione storica: nel linguaggio ABC che ha preceduto Python (ed era molto influente nel suo design), ci fu un operatore unario scritto come #sche significava len(s).


12
met% python -c 'import this' | grep 'only one'
There should be one-- and preferably only one --obvious way to do it.

34
La cosa ovvia quando si lavora con un oggetto è, ovviamente, un metodo.
Peter Cooper,

4
@Peter: pagherei $ 20 a chiunque abbia prove fotografiche che hanno registrato il tuo commento alle spalle di Guido. $ 50 se è sulla sua fronte.
bug

1
Sì, i designer di Python aderiscono al dogma ma loro stessi non rispettano il proprio dogma.
Bitek,

2
$ python -c 'importa questo' | grep ovvio
Ed Randall,

4

Ci sono alcune risposte fantastiche qui, e quindi prima di dare la mia vorrei evidenziare alcune delle gemme (nessun gioco di rubini previsto) che ho letto qui.

  • Python non è un linguaggio OOP puro: è un linguaggio multi-paradigma per scopi generali che consente al programmatore di utilizzare il paradigma con cui si trova più a suo agio e / o il paradigma più adatto alla propria soluzione.
  • Python ha funzioni di prima classe, quindi in lenrealtà è un oggetto. Ruby, d'altra parte, non ha funzioni di prima classe. Quindi l' lenoggetto funzione ha i propri metodi che è possibile controllare eseguendo dir(len).

Se non ti piace il modo in cui funziona nel tuo codice, è banale implementare nuovamente i contenitori usando il tuo metodo preferito (vedi esempio sotto).

>>> class List(list):
...     def len(self):
...         return len(self)
...
>>> class Dict(dict):
...     def len(self):
...         return len(self)
...
>>> class Tuple(tuple):
...     def len(self):
...         return len(self)
...
>>> class Set(set):
...     def len(self):
...         return len(self)
...
>>> my_list = List([1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'])
>>> my_dict = Dict({'key': 'value', 'site': 'stackoverflow'})
>>> my_set = Set({1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'})
>>> my_tuple = Tuple((1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'))
>>> my_containers = Tuple((my_list, my_dict, my_set, my_tuple))
>>>
>>> for container in my_containers:
...     print container.len()
...
15
2
15
15

0

Puoi anche dire

>> x = 'test'
>> len(x)
4

Utilizzo di Python 2.7.3.


9
lenla funzione era già stata menzionata nelle risposte precedenti. Qual è il punto di questa "risposta", allora?
Piotr Dobrogost,

0

Qualcosa che manca dal resto delle risposte qui: la lenfunzione verifica che il __len__metodo restituisca un valore non negativo int. Il fatto che lensia una funzione significa che le classi non possono ignorare questo comportamento per evitare il controllo. Come tale, len(obj)offre un livello di sicurezza che obj.len()non può.

Esempio:

>>> class A:
...     def __len__(self):
...         return 'foo'
...
>>> len(A())
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    len(A())
TypeError: 'str' object cannot be interpreted as an integer
>>> class B:
...     def __len__(self):
...         return -1
... 
>>> len(B())
Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    len(B())
ValueError: __len__() should return >= 0

Naturalmente, è possibile "sovrascrivere" la lenfunzione riassegnandola come variabile globale, ma il codice che lo fa è molto più evidentemente sospetto del codice che sovrascrive un metodo in una classe.


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.