underscore - python style guide




Come si restituiscono più valori in Python? (10)

"Migliore" è una decisione parzialmente soggettiva. Utilizzare tuple per insiemi di ritorno piccoli nel caso generale in cui un valore immutabile è accettabile. Una tupla è sempre preferibile a un elenco quando la mutabilità non è un requisito.

Per valori di ritorno più complessi, o nel caso in cui la formalità sia valida (ad es. Codice di alto valore) una tupla con nome è migliore. Per il caso più complesso un oggetto è solitamente il migliore. Tuttavia, è davvero la situazione che conta. Se ha senso restituire un oggetto perché questo è ciò che si ha naturalmente alla fine della funzione (es. Pattern di fabbrica) quindi restituire l'oggetto.

Come disse il saggio:

L'ottimizzazione prematura è la radice di tutto il male (o almeno la maggior parte di esso) nella programmazione.

Il modo canonico per restituire più valori nelle lingue che lo supportano è spesso tupling .

Opzione: utilizzo di una tupla

Considera questo esempio banale:

def f(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return (y0,y1,y2)

Tuttavia, questo diventa rapidamente problematico al crescere del numero di valori restituiti. Cosa succede se si desidera restituire quattro o cinque valori? Certo, si potrebbe continuare a tingerli, ma diventa facile dimenticare quale valore è dove. È anche piuttosto brutto disfare i bagagli ovunque tu voglia riceverli.

Opzione: utilizzo di un dizionario

Il prossimo passo logico sembra essere quello di introdurre una sorta di "annotazione del record". In Python, il modo più ovvio per farlo è tramite un dict .

Considera quanto segue:

def g(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return {'y0':y0, 'y1':y1 ,'y2':y2 }

(edit- Solo per essere chiari, y0, y1 e y2 sono solo intesi come identificatori astratti. Come sottolineato, in pratica useresti identificatori significativi)

Ora, abbiamo un meccanismo con cui possiamo proiettare un particolare membro dell'oggetto restituito. Per esempio,

result['y0']

Opzione: utilizzo di una classe

Tuttavia, c'è un'altra opzione. Potremmo invece restituire una struttura specializzata. L'ho inquadrato nel contesto di Python, ma sono sicuro che si applica anche ad altre lingue. In effetti, se lavorassi in C questa potrebbe essere la tua unica opzione. Ecco qui:

class ReturnValue(object):
  def __init__(self, y0, y1, y2):
     self.y0 = y0
     self.y1 = y1
     self.y2 = y2

def g(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return ReturnValue(y0, y1, y2)

In Python i precedenti due sono forse molto simili in termini di plumbing. Dopo tutto, { y0, y1, y2 } finiscono per essere le voci nel __dict__ interno del ReturnValue .

C'è una funzionalità aggiuntiva fornita da Python per oggetti piccoli, l'attributo __slots__ . La classe potrebbe essere espressa come:

class ReturnValue(object):
  __slots__ = ["y0", "y1", "y2"]
  def __init__(self, y0, y1, y2):
     self.y0 = y0
     self.y1 = y1
     self.y2 = y2

Dal manuale di riferimento di Python :

La dichiarazione __slots__ accetta una sequenza di variabili di istanza e riserva lo spazio sufficiente in ogni istanza per contenere un valore per ogni variabile. Lo spazio viene salvato perché __dict__ non viene creato per ogni istanza.

Opzione: utilizzo di un elenco

Un altro suggerimento che ho trascurato viene da Bill the Lizard:

def h(x):
  result = [x + 1]
  result.append(x * 3)
  result.append(y0 ** y3)
  return result

Questo è il mio metodo meno preferito però. Suppongo di essere contaminato dall'esposizione a Haskell, ma l'idea di liste miste è sempre stata a disagio per me. In questo particolare esempio l'elenco è di tipo non misto, ma in teoria potrebbe essere. Una lista usata in questo modo non guadagna nulla per quanto riguarda la tupla, per quanto ne so io. L'unica vera differenza tra liste e tuple in Python è che le liste sono mutable , mentre le tuple no. Personalmente tendo a trasferire le convenzioni dalla programmazione funzionale: uso liste per qualsiasi numero di elementi dello stesso tipo e tuple per un numero fisso di elementi di tipi predeterminati.

Domanda

Dopo il lungo preambolo, arriva l'inevitabile domanda. Quale metodo (pensi) è il migliore?

In genere mi sono trovato a percorrere il percorso del dizionario perché comporta un minor lavoro di set-up. Dal punto di vista dei tipi, tuttavia, è meglio seguire la rotta della classe, poiché ciò potrebbe aiutarti a evitare di confondere ciò che rappresenta un dizionario. D'altra parte, ci sono alcuni nella comunità Python che ritengono che le interfacce implicite dovrebbero essere preferite alle interfacce esplicite , a quel punto il tipo di oggetto non è realmente rilevante, dal momento che si basa fondamentalmente sulla convenzione che lo stesso attributo avrà sempre lo stesso significato.

Quindi, come si fa a restituire più valori in Python?


+1 sul suggerimento di S.Lott di una classe contenitore denominata.

Per python 2.6 e versioni successive, una tupla denominata fornisce un modo utile per creare facilmente queste classi contenitore e i risultati sono "leggeri e non richiedono più memoria rispetto alle tuple normali".


In linguaggi come Python, di solito utilizzo un dizionario perché comporta un sovraccarico minore rispetto alla creazione di una nuova classe.

Tuttavia, se mi trovo costantemente a restituire lo stesso insieme di variabili, allora questo probabilmente implica una nuova classe che tratterò.


Io voto per il dizionario.

Trovo che se faccio una funzione che restituisce qualcosa di più di 2-3 variabili, le piegherò in un dizionario. Altrimenti tendo a dimenticare l'ordine e il contenuto di ciò che sto tornando.

Inoltre, l'introduzione di una struttura "speciale" rende il tuo codice più difficile da seguire. (Qualcun altro dovrà cercare attraverso il codice per scoprire di cosa si tratta)

Se sei preoccupato di cercare il tipo, usa le chiavi descrittive del dizionario, ad esempio "elenco x-valori".

def g(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return {'y0':y0, 'y1':y1 ,'y2':y2 }

Molte risposte suggeriscono che è necessario restituire una raccolta di qualche tipo, come un dizionario o una lista. Si potrebbe omettere la sintassi aggiuntiva e scrivere solo i valori di ritorno, separati da virgola. Nota: questo restituisce tecnicamente una tupla.

def f():
    return True, False
x, y = f()
print(x)
print(y)

dà:

True
False

Per i piccoli progetti trovo più facile lavorare con le tuple. Quando diventa troppo difficile da gestire (e non prima), comincio a raggruppare le cose in strutture logiche, tuttavia penso che l'uso suggerito di dizionari e oggetti ReturnValue sia sbagliato (o troppo semplicistico).

Restituire un dizionario con le chiavi y0, y1, y2 ecc non offre alcun vantaggio rispetto alle tuple. Restituire un'istanza ReturnValue con proprietà .y0 .y1 .y2 ecc non offre alcun vantaggio rispetto alle tuple. Devi iniziare a dare un nome alle cose se vuoi arrivare da qualche parte, e puoi farlo usando comunque le tuple:

def getImageData(filename):
  [snip]
  return size, (format, version, compression), (width,height)
size, type, dimensions = getImageData(x)

IMHO, l'unica buona tecnica oltre le tuple è quella di restituire oggetti reali con metodi e proprietà corrette, come si ottiene da re.match() o open(file) .


Un'altra opzione potrebbe essere l'utilizzo di generatori:

>>> def f(x):
        y0 = x + 1
        yield y0
        yield x * 3
        yield y0 ** 4


>>> a, b, c = f(5)
>>> a
6
>>> b
15
>>> c
1296

Sebbene le tuple IMHO siano solitamente le migliori, tranne nei casi in cui i valori restituiti sono candidati per l'incapsulamento in una classe.


Vorrei usare un dict per passare e restituire valori da una funzione:

Usa la forma variabile come definito nel form .

form = {
    'level': 0,
    'points': 0,
    'game': {
        'name': ''
    }
}


def test(form):
    form['game']['name'] = 'My game!'
    form['level'] = 2

    return form

>>> print(test(form))
{u'game': {u'name': u'My game!'}, u'points': 0, u'level': 2}

Questo è il modo più efficiente per me e per l'unità di elaborazione.

Devi passare solo un puntatore e restituire solo un puntatore.

Non è necessario modificare le funzioni (migliaia di argomenti) ogni volta che si modifica il codice.


Tuple nominate sono state aggiunte in 2.6 per questo scopo. Vedi anche os.stat per un esempio simile incorporato.

>>> import collections
>>> Point = collections.namedtuple('Point', ['x', 'y'])
>>> p = Point(1, y=2)
>>> p.x, p.y
1 2
>>> p[0], p[1]
1 2

Nelle recenti versioni di Python 3 (3.6+, penso), la nuova libreria di typing ottenuto la classe NamedTuple per rendere più tuple denominate più facili da creare e più potenti. Ereditare da typing.NamedTuple consente di utilizzare docstring, valori predefiniti e digitare annotazioni.

Esempio (dai documenti):

class Employee(NamedTuple):  # inherit from collections.NamedTuple
    name: str
    id: int = 3  # default value

employee = Employee('Guido')
assert employee.id == 3

>>> def func():
...    return [1,2,3]
...
>>> a,b,c = func()
>>> a
1
>>> b
2
>>> c
3




return-value