dequeue - python namedtuple access by name




Cosa sono "tuple con nome" in Python? (7)

Leggendo le modifiche in Python 3.1 , ho trovato qualcosa ... inaspettato:

La tupla sys.version_info è ora una tupla con nome :

Non ho mai sentito parlare di tuple prima d'ora e pensavo che gli elementi potessero essere indicizzati con numeri (come in tuple e liste) o con i tasti (come in dicts). Non mi sarei mai aspettato che potessero essere indicizzati in entrambi i modi.

Quindi, le mie domande sono:

  • Cosa sono le tuple?
  • Come usarli?
  • Perché / quando dovrei usare tuple con nome invece delle normali tuple?
  • Perché / quando dovrei usare tuple normali invece di tuple con nome?
  • Esiste un qualche tipo di "lista nominata" (una versione mutabile della tupla chiamata)?

Cosa sono le tuple?

Una tupla chiamata è una tupla.

Fa tutto ciò che una tupla può fare.

Ma è più di una semplice tupla.

È una sottoclasse specifica di una tupla creata a livello di programmazione per le specifiche, con campi denominati e una lunghezza fissa.

Questo, ad esempio, crea una sottoclasse di tuple e, oltre ad essere di lunghezza fissa (in questo caso tre), può essere utilizzata ovunque si usi una tupla senza rottura. Questo è noto come sostituibilità di Liskov:

>>> from collections import namedtuple
>>> class_name = 'ANamedTuple'
>>> fields = 'foo bar baz'
>>> ANamedTuple = namedtuple(class_name, fields)

Questo lo istanzia:

>>> ant = ANamedTuple(1, 'bar', [])

Possiamo ispezionarlo e usare i suoi attributi:

>>> ant
ANamedTuple(foo=1, bar='bar', baz=[])
>>> ant.foo
1
>>> ant.bar
'bar'
>>> ant.baz.append('anything')
>>> ant.baz
['anything']

Spiegazione più profonda

Per capire le tuple nominate, devi prima sapere che cos'è una tupla. Una tupla è essenzialmente una lista immutabile (non può essere modificata sul posto nella memoria).

Ecco come potresti usare una normale tupla:

>>> student_tuple = 'Lisa', 'Simpson', 'A'
>>> student_tuple
('Lisa', 'Simpson', 'A')
>>> student_tuple[0]
'Lisa'
>>> student_tuple[1]
'Simpson'
>>> student_tuple[2]
'A'

Puoi espandere una tupla con il disimballaggio iterabile:

>>> first, last, grade = student_tuple
>>> first
'Lisa'
>>> last
'Simpson'
>>> grade
'A'

Le tuple nominate sono tuple che permettono ai loro elementi di accedere per nome invece che per indice!

Fai una namedtuple in questo modo:

>>> from collections import namedtuple
>>> Student = namedtuple('Student', ['first', 'last', 'grade'])

Puoi anche utilizzare una singola stringa con i nomi separati da spazi, un uso leggermente più leggibile dell'API:

>>> Student = namedtuple('Student', 'first last grade')

Come usarli?

Puoi fare tutto ciò che le tuple possono fare (vedi sopra) e fare quanto segue:

>>> named_student_tuple = Student('Lisa', 'Simpson', 'A')
>>> named_student_tuple.first
'Lisa'
>>> named_student_tuple.last
'Simpson'
>>> named_student_tuple.grade
'A'
>>> named_student_tuple._asdict()
OrderedDict([('first', 'Lisa'), ('last', 'Simpson'), ('grade', 'A')])
>>> vars(named_student_tuple)
OrderedDict([('first', 'Lisa'), ('last', 'Simpson'), ('grade', 'A')])
>>> new_named_student_tuple = named_student_tuple._replace(first='Bart', grade='C')
>>> new_named_student_tuple
Student(first='Bart', last='Simpson', grade='C')

Un commentatore ha chiesto:

In un grande script o programma, dove solitamente si definisce una tupla chiamata?

I tipi che crei con namedtuple sono fondamentalmente classi che puoi creare con una semplice stenografia. Trattali come se fossero classi. Definirli a livello di modulo, in modo che pickle e altri utenti possano trovarli.

L'esempio di lavoro, a livello di modulo globale:

>>> from collections import namedtuple
>>> NT = namedtuple('NT', 'foo bar')
>>> nt = NT('foo', 'bar')
>>> import pickle
>>> pickle.loads(pickle.dumps(nt))
NT(foo='foo', bar='bar')

E questo dimostra l'incapacità di cercare la definizione:

>>> def foo():
...     LocalNT = namedtuple('LocalNT', 'foo bar')
...     return LocalNT('foo', 'bar')
... 
>>> pickle.loads(pickle.dumps(foo()))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
_pickle.PicklingError: Can't pickle <class '__main__.LocalNT'>: attribute lookup LocalNT on __main__ failed

Perché / quando dovrei usare tuple con nome invece delle normali tuple?

Usali quando migliora il tuo codice per avere la semantica degli elementi di tupla espressi nel tuo codice. Puoi usarli al posto di un oggetto se altrimenti utilizzi un oggetto con attributi di dati invariabili e nessuna funzionalità. Puoi anche creare una sottoclasse per aggiungere funzionalità, ad esempio :

class Point(namedtuple('Point', 'x y')):
    """adding functionality to a named tuple"""
        __slots__ = ()
        @property
        def hypot(self):
            return (self.x ** 2 + self.y ** 2) ** 0.5
        def __str__(self):
            return 'Point: x=%6.3f  y=%6.3f  hypot=%6.3f' % (self.x, self.y, self.hypot)

Perché / quando dovrei usare tuple normali invece di tuple con nome?

Probabilmente sarebbe una regressione passare dall'uso di tuple con nome a tuple. La decisione di progettazione iniziale è incentrata sul fatto che il costo del codice aggiuntivo in questione valga la leggibilità migliorata quando viene utilizzata la tupla.

Non vi è memoria aggiuntiva utilizzata dalle tuple denominate rispetto alle tuple.

Esiste un qualche tipo di "lista nominata" (una versione mutabile della tupla chiamata)?

Stai cercando un oggetto a fessura che implementa tutte le funzionalità di un elenco di dimensioni statiche o un elenco sottoclasse che funziona come una tupla con nome (e che in qualche modo blocca l'elenco dalla modifica delle dimensioni).

Un esempio ora ampliato, e forse addirittura sostituibile con Liskov, del primo:

from collections import Sequence

class MutableTuple(Sequence): 
    """Abstract Base Class for objects that work like mutable
    namedtuples. Subclass and define your named fields with 
    __slots__ and away you go.
    """
    __slots__ = ()
    def __init__(self, *args):
        for slot, arg in zip(self.__slots__, args):
            setattr(self, slot, arg)
    def __repr__(self):
        return type(self).__name__ + repr(tuple(self))
    # more direct __iter__ than Sequence's
    def __iter__(self): 
        for name in self.__slots__:
            yield getattr(self, name)
    # Sequence requires __getitem__ & __len__:
    def __getitem__(self, index):
        return getattr(self, self.__slots__[index])
    def __len__(self):
        return len(self.__slots__)

E per usare, basta sottoclasse e definisci __slots__ :

class Student(MutableTuple):
    __slots__ = 'first', 'last', 'grade' # customize 


>>> student = Student('Lisa', 'Simpson', 'A')
>>> student
Student('Lisa', 'Simpson', 'A')
>>> first, last, grade = student
>>> first
'Lisa'
>>> last
'Simpson'
>>> grade
'A'
>>> student[0]
'Lisa'
>>> student[2]
'A'
>>> len(student)
3
>>> 'Lisa' in student
True
>>> 'Bart' in student
False
>>> student.first = 'Bart'
>>> for i in student: print(i)
... 
Bart
Simpson
A

namedtuple

è uno dei modi più semplici per ripulire il tuo codice e renderlo più leggibile. Si auto-documenta cosa sta succedendo nella tupla. Le istanze di Namedtuples sono efficienti quanto la memoria delle tuple normali in quanto non hanno dizionari per istanza, rendendoli più veloci dei dizionari.

from collections import namedtuple

Color = namedtuple('Color', ['hue', 'saturation', 'luminosity'])

 p = Color(170, 0.1, 0.6)
 if p.saturation >= 0.5:
     print "Whew, that is bright!"
 if p.luminosity >= 0.5:
     print "Wow, that is light"

Senza nominare ogni elemento nella tupla, si legge così:

p = (170, 0.1, 0.6)
if p[1] >= 0.5:
    print "Whew, that is bright!"
if p[2]>= 0.5:
   print "Wow, that is light"

È molto più difficile capire cosa sta succedendo nel primo esempio. Con un namedtuple, ogni campo ha un nome. E tu accedi ad esso per nome invece che per posizione o indice. Invece di p[1] , possiamo chiamarlo p.saturation. È più facile da capire E sembra più pulito.

Creare un'istanza di namedtuple è più semplice che creare un dizionario.

# dictionary
>>>p = dict(hue = 170, saturation = 0.1, luminosity = 0.6)
>>>p['hue']
170

#nametuple
>>>from collections import namedtuple
>>>Color = namedtuple('Color', ['hue', 'saturation', 'luminosity'])
>>>p = Color(170, 0.1, 0.6)
>>>p.hue
170

Quando potresti usare namedtuple

  1. Come appena detto, il namedtuple rende molto più semplice la comprensione delle tuple. Quindi, se hai bisogno di fare riferimento agli elementi nella tupla, la creazione di questi come tuple ha senso.
  2. Oltre ad essere più leggero di un dizionario, namedtuple mantiene anche l'ordine diverso dal dizionario.
  3. Come nell'esempio sopra, è più semplice creare un'istanza di namedtuple piuttosto che un dizionario. E fare riferimento all'elemento nella tupla chiamata sembra più pulito di un dizionario. p.hue piuttosto che p['hue'] .

La sintassi

collections.namedtuple(typename, field_names[, verbose=False][, rename=False])
  • namedtuple è nella libreria delle collezioni.
  • typename: questo è il nome della nuova sottoclasse di tuple.
  • field_names: una sequenza di nomi per ogni campo. Può essere una sequenza come in una lista ['x', 'y', 'z'] o stringa xyz (senza virgole, solo spazi bianchi) o x, y, z .
  • rinomina: se rinomina è True , i nomi dei campi non validi vengono automaticamente sostituiti con nomi posizionali. Ad esempio, ['abc', 'def', 'ghi','abc'] è convertito in ['abc', '_1', 'ghi', '_3'] , eliminando la parola chiave 'def' (dal quel è una parola riservata per definire le funzioni) e il nome di campo duplicato 'abc' .
  • verbose: se verbose è True , la definizione della classe viene stampata prima di essere creata.

Puoi ancora accedere a namedtuples in base alla loro posizione, se lo desideri. p[1] == p.saturation . Si spacchetta ancora come una normale tupla.

metodi

Sono supportati tutti i normali metodi di tuple . Es: min (), max (), len (), in, not in, concatenation (+), index, slice, ecc. E ce ne sono alcuni aggiuntivi per namedtuple. Nota: tutti iniziano con un trattino basso. _replace , _make , _asdict .

_replace Restituisce una nuova istanza della tupla denominata che sostituisce i campi specificati con nuovi valori.

La sintassi

somenamedtuple._replace(kwargs)

Esempio

>>>from collections import namedtuple

>>>Color = namedtuple('Color', ['hue', 'saturation', 'luminosity'])
>>>p = Color(170, 0.1, 0.6)

>>>p._replace(hue=87)
Color(87, 0.1, 0.6)

>>>p._replace(hue=87, saturation=0.2)
Color(87, 0.2, 0.6)

Avviso : i nomi dei campi non sono tra virgolette; sono parole chiave qui. Ricorda : le tuple sono immutabili, anche se sono denominate tuple e hanno il metodo _replace . _replace produce una new istanza; non modifica l'originale o sostituisce il vecchio valore. Ovviamente puoi salvare il nuovo risultato nella variabile. p = p._replace(hue=169)

_make

Crea una nuova istanza da una sequenza esistente o iterabile.

La sintassi

somenamedtuple._make(iterable)

Esempio

 >>>data = (170, 0.1, 0.6)
 >>>Color._make(data)
Color(hue=170, saturation=0.1, luminosity=0.6)

>>>Color._make([170, 0.1, 0.6])  #the list is an iterable
Color(hue=170, saturation=0.1, luminosity=0.6)

>>>Color._make((170, 0.1, 0.6))  #the tuple is an iterable
Color(hue=170, saturation=0.1, luminosity=0.6)

>>>Color._make(170, 0.1, 0.6) 
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "<string>", line 15, in _make
TypeError: 'float' object is not callable

Cosa è successo con l'ultimo? L'elemento all'interno della parentesi dovrebbe essere il iterable. Quindi una lista o una tupla all'interno della parentesi funziona, ma la sequenza di valori senza includere un iterable restituisce un errore.

_asdict

Restituisce un nuovo OrderedDict che associa i nomi dei campi ai valori corrispondenti.

La sintassi

somenamedtuple._asdict()

Esempio

 >>>p._asdict()
OrderedDict([('hue', 169), ('saturation', 0.1), ('luminosity', 0.6)])

Riferimento : https://www.reddit.com/r/Python/comments/38ee9d/intro_to_namedtuple/

C'è anche una lista chiamata simile alla tupla chiamata ma mutabile https://pypi.python.org/pypi/namedlist


In Python all'interno c'è un buon uso del contenitore chiamato tupla con nome, può essere usato per creare una definizione di classe e ha tutte le caratteristiche della tupla originale.

L'utilizzo della tupla denominata verrà applicata direttamente al modello di classe predefinito per generare una classe semplice, questo metodo consente a molti codici di migliorare la leggibilità ed è anche molto utile quando si definisce una classe.


Prova questo:

collections.namedtuple()

Fondamentalmente, namedtuples sono facili da creare, tipi di oggetti leggeri. Trasformano le tuple in contenitori convenienti per compiti semplici. Con namedtuples , non è necessario utilizzare indici integer per accedere ai membri di una tupla.

Esempi:

Codice 1:

>>> from collections import namedtuple

>>> Point = namedtuple('Point','x,y')

>>> pt1 = Point(1,2)

>>> pt2 = Point(3,4)

>>> dot_product = ( pt1.x * pt2.x ) +( pt1.y * pt2.y )

>>> print dot_product
11

Codice 2:

>>> from collections import namedtuple

>>> Car = namedtuple('Car','Price Mileage Colour Class')

>>> xyz = Car(Price = 100000, Mileage = 30, Colour = 'Cyan', Class = 'Y')

>>> print xyz

Car(Price=100000, Mileage=30, Colour='Cyan', Class='Y')
>>> print xyz.Class
Y

namedtuples sono una grande caratteristica, sono un contenitore perfetto per i dati. Quando devi "memorizzare" i dati useresti tuple o dizionari, come:

user = dict(name="John", age=20)

o:

user = ("John", 20)

L'approccio del dizionario è schiacciante, dal momento che i dict sono mutabili e più lenti delle tuple. D'altra parte, le tuple sono immutabili e leggere, ma mancano di leggibilità per un gran numero di voci nei campi dati.

namedtuples sono il compromesso perfetto per i due approcci, hanno una grande leggibilità, leggerezza e immutabilità (in più sono polimorfici!).


tuple con nome consentono la compatibilità con il codice che controlla la versione come questa

>>> sys.version_info[0:2]
(3, 1)

consentendo al futuro codice di essere più esplicito usando questa sintassi

>>> sys.version_info.major
3
>>> sys.version_info.minor
1

Le tuple nominate sono fondamentalmente tipi di oggetti leggeri e facili da creare. Le istanze di tuple nominate possono essere referenziate usando la dereferenziazione variabile di oggetti o la sintassi della tupla standard. Possono essere utilizzati in modo simile a struct o altri tipi di record comuni, tranne per il fatto che sono immutabili. Sono stati aggiunti in Python 2.6 e Python 3.0, anche se esiste una ricetta per l'implementazione in Python 2.4 .

Ad esempio, è comune rappresentare un punto come una tupla (x, y) . Questo porta al codice come il seguente:

pt1 = (1.0, 5.0)
pt2 = (2.5, 1.5)

from math import sqrt
line_length = sqrt((pt1[0]-pt2[0])**2 + (pt1[1]-pt2[1])**2)

Usando una tupla nominata diventa più leggibile:

from collections import namedtuple
Point = namedtuple('Point', 'x y')
pt1 = Point(1.0, 5.0)
pt2 = Point(2.5, 1.5)

from math import sqrt
line_length = sqrt((pt1.x-pt2.x)**2 + (pt1.y-pt2.y)**2)

Tuttavia, le tuple con nome sono ancora retrocompatibili con le normali tuple, quindi quanto segue funzionerà ancora:

Point = namedtuple('Point', 'x y')
pt1 = Point(1.0, 5.0)
pt2 = Point(2.5, 1.5)

from math import sqrt
# use index referencing
line_length = sqrt((pt1[0]-pt2[0])**2 + (pt1[1]-pt2[1])**2)
 # use tuple unpacking
x1, y1 = pt1

Pertanto, dovresti usare tuple con nome anziché tuple ovunque tu pensi che la notazione degli oggetti renderà il tuo codice più pietonico e più facilmente leggibile . Personalmente ho iniziato a usarli per rappresentare dei tipi di valori molto semplici, in particolare quando li si passa come parametri alle funzioni. Rende le funzioni più leggibili, senza vedere il contesto dell'imballaggio della tupla.

Inoltre, puoi anche sostituire classi ordinarie immutabili che non hanno funzioni , solo campi con esse. Puoi anche usare i tuoi tipi di tuple nominati come classi base:

class Point(namedtuple('Point', 'x y')):
    [...]

Tuttavia, come con le tuple, gli attributi nelle tuple nominate sono immutabili:

>>> Point = namedtuple('Point', 'x y')
>>> pt1 = Point(1.0, 5.0)
>>> pt1.x = 2.0
AttributeError: can't set attribute

Se vuoi poter cambiare i valori, hai bisogno di un altro tipo. Esiste una pratica ricetta per i tipi di record mutabili che consentono di impostare nuovi valori per gli attributi.

>>> from rcdtype import *
>>> Point = recordtype('Point', 'x y')
>>> pt1 = Point(1.0, 5.0)
>>> pt1 = Point(1.0, 5.0)
>>> pt1.x = 2.0
>>> print(pt1[0])
    2.0

Non sono a conoscenza di alcuna forma di "lista nominata" che ti permetta di aggiungere nuovi campi, comunque. Puoi semplicemente voler usare un dizionario in questa situazione. Le tuple con nome possono essere convertite in dizionari usando pt1._asdict() che restituisce {'x': 1.0, 'y': 5.0} e può essere utilizzato con tutte le normali funzioni del dizionario.

Come già notato, dovresti controllare la documentazione per maggiori informazioni da cui sono stati costruiti questi esempi.





namedtuple