I vantaggi di avere una funzione statica come len (), max () e min () rispetto alle chiamate ai metodi ereditate

StackOverflow https://stackoverflow.com/questions/1628222

  •  06-07-2019
  •  | 
  •  

Domanda

Sono un principiante di Python e non sono sicuro del motivo per cui Python ha implementato len (obj), max (obj) e min (obj) come funzioni statiche (vengo dal linguaggio java) su obj.len ( ), obj.max () e obj.min ()

quali sono i vantaggi e gli svantaggi (oltre all'ovvia incoerenza) di avere len () ... rispetto alle chiamate del metodo?

perché Guido ha scelto questo tra le chiamate del metodo? (questo avrebbe potuto essere risolto in python3 se necessario, ma non è stato modificato in python3, quindi ci devono essere buoni motivi ... spero)

grazie !!

È stato utile?

Soluzione

Il grande vantaggio è che le funzioni integrate (e gli operatori) possono applicare una logica extra quando appropriato, oltre a chiamare semplicemente i metodi speciali. Ad esempio, min può esaminare diversi argomenti e applicare i controlli di disuguaglianza appropriati, oppure può accettare un singolo argomento iterabile e procedere in modo simile; abs quando chiamato su un oggetto senza un metodo speciale __abs__ potrebbe provare a confrontare detto oggetto con 0 e utilizzare il metodo di cambio oggetto se necessario (anche se attualmente non lo fa); e così via.

Quindi, per coerenza, tutte le operazioni con ampia applicabilità devono sempre passare attraverso built-in e / o operatori, ed è la responsabilità di questi built-in cercare e applicare i metodi speciali appropriati (su uno o più degli argomenti) , utilizzare la logica alternativa ove applicabile e così via.

Un esempio in cui questo principio non è stato applicato correttamente (ma l'incongruenza è stata risolta in Python 3) è "far avanzare un iteratore" in 2.5 e precedenti, era necessario definire e chiamare il nome non appositamente < codice> prossimo sull'iteratore. In 2.6 e versioni successive puoi farlo nel modo giusto: l'oggetto iteratore definisce __next__ , il nuovo next incorporato può chiamarlo e si applicano logica aggiuntiva, ad esempio per fornire un valore predefinito (in 2.6 puoi ancora farlo alla vecchia maniera, per compatibilità con le versioni precedenti, sebbene in 3. * non puoi più).

Un altro esempio: considera l'espressione x + y . In un linguaggio tradizionale orientato agli oggetti (in grado di inviare solo sul tipo dell'argomento più a sinistra - come Python, Ruby, Java, C ++, C #, e amp; c) se x è di alcuni- in type e y è del tuo nuovo tipo di fantasia, sei sfortunatamente sfortunato se la lingua insiste nel delegare tutta la logica al metodo di type (x) che implementa l'aggiunta (supponendo che la lingua consenta il sovraccarico dell'operatore ;-).

In Python, l'operatore + (e allo stesso modo naturalmente l'operatore operator.add incorporato, se è quello che preferisci) prova il __add __ e se quello non sa cosa fare con y , prova il __radd__ del tipo y. Quindi puoi definire i tuoi tipi che sanno come aggiungersi a numeri interi, float, complessi, ecc., Così come quelli che sanno come aggiungere tali tipi numerici incorporati a se stessi (cioè, puoi codificarli in modo che x + y e y + x funzionano entrambi bene, quando y è un'istanza del tuo nuovo tipo di fantasia e x è un'istanza di un tipo numerico incorporato).

" Funzioni generiche " (come in PEAK) sono un approccio più elegante (che consente qualsiasi override basato su una combinazione di tipi, mai con la folle attenzione monomaniaca sugli argomenti più a sinistra che OOP incoraggia! -), ma (a) essi purtroppo non sono stati accettati per Python 3 e (b) ovviamente richiedono che la funzione generica sia espressa come indipendente (sarebbe assolutamente folle dover considerare la funzione come "appartenente" a qualsiasi singolo tipo, dove l'intero PUNTO è che può essere diversamente sostituito / sovraccaricato sulla base di una combinazione arbitraria dei vari tipi di argomenti! -). Chiunque abbia mai programmato in Common Lisp, Dylan o PEAK, sa di cosa sto parlando ;-).

Quindi, le funzioni e gli operatori indipendenti sono proprio IL modo giusto e coerente di procedere (anche se la mancanza di funzioni generiche, in Python a ossa nude, rimuove alcune frazione dell'eleganza intrinseca , è ancora un ragionevole mix di eleganza e praticità! -).

Altri suggerimenti

Sottolinea le capacità di un oggetto, non i suoi metodi o tipo. Le capacità sono dichiarate da "helper" funzioni come __iter__ e __len__ ma non costituiscono l'interfaccia. L'interfaccia è nelle funzioni integrate e accanto anche agli operatori integrati come + e [] per l'indicizzazione e il slicing.

A volte, non è una corrispondenza individuale: ad esempio, iter (obj) restituisce un iteratore per un oggetto e funzionerà anche se __iter__ non è definito. Se non definito, continua a guardare se l'oggetto definisce __getitem__ e restituirà un iteratore che accede all'oggetto in modo indicizzato (come un array).

Questo si accompagna alla Duck Typing di Python, ci preoccupiamo solo di cosa possiamo fare con un oggetto, non che sia di un tipo particolare.

In realtà, quelli non sono " statici " metodi nel modo in cui ci stai pensando. Sono funzioni integrate che in realtà si limitano a definire alcuni metodi su oggetti Python che implementano loro.

>>> class Foo(object):
...     def __len__(self):
...             return 42
... 
>>> f = Foo()
>>> len(f)
42

Questi sono sempre disponibili per essere chiamati indipendentemente dal fatto che l'oggetto li attui o meno. Il punto è avere una certa coerenza. Invece che una classe abbia un metodo chiamato length () e un altro chiamato size (), la convenzione è quella di implementare len e consentire ai chiamanti di accedervi sempre con il più leggibile len (obj) anziché obj. methodThatDoesSomethingCommon

Pensavo che il motivo fosse che queste operazioni di base potevano essere eseguite su iteratori con la stessa interfaccia dei container. Tuttavia, in realtà non funziona con len:

def foo():
    for i in range(10):
        yield i
print len(foo())

... non riesce con TypeError. len () non consumerà e conterà un iteratore; funziona solo con oggetti che hanno una chiamata __len__ .

Quindi, per quanto mi riguarda, len () non dovrebbe esistere. È molto più naturale dire obj.len che len (obj) e molto più coerente con il resto della lingua e la libreria standard. Non diciamo append (lst, 1); diciamo lst.append (1). Avere un metodo globale separato per la lunghezza è un caso speciale strano e incoerente e mangia un nome molto ovvio nello spazio dei nomi globale, che è una pessima abitudine di Python.

Non è correlato alla tipizzazione anatra; puoi dire getattr (obj, " len ") per decidere se puoi usare len su un oggetto altrettanto facilmente - e molto più coerentemente - di quanto puoi usare getattr (obj , " __ len __ ") .

Tutto ciò che ha detto, mentre le verruche linguistiche vanno - per coloro che lo considerano una verruca - questo è molto facile con cui convivere.

D'altra parte, min e max fanno lavorano su iteratori, il che dà loro un uso a parte qualsiasi oggetto particolare. Questo è semplice, quindi farò solo un esempio:

import random
def foo():
    for i in range(10):
        yield random.randint(0, 100)
print max(foo())

Tuttavia, non esistono metodi __min__ o __max__ per sovrascriverne il comportamento, quindi non esiste un modo coerente per fornire una ricerca efficiente di contenitori ordinati. Se un contenitore è ordinato sulla stessa chiave che stai cercando, min / max sono operazioni O (1) invece di O (n) e l'unico modo per esporre è tramite un metodo diverso e incoerente. (Questo potrebbe essere risolto nella lingua relativamente facilmente, ovviamente.)

Per far fronte a un altro problema con questo: impedisce l'uso dell'associazione del metodo di Python. Come esempio semplice e inventato, puoi farlo per fornire una funzione per aggiungere valori a un elenco:

def add(f):
    f(1)
    f(2)
    f(3)
lst = []
add(lst.append)
print lst

e funziona su tutte le funzioni membro. Tuttavia, non puoi farlo con min, max o len, poiché non sono metodi dell'oggetto su cui operano. Invece, devi ricorrere a functools.partial, una soluzione goffa di seconda classe comune in altre lingue.

Naturalmente, questo è un caso insolito; ma sono i casi non comuni che ci parlano della coerenza di una lingua.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top