开发者

What are "named tuples" in Python?

  • What are named tuples and how do I use them?
  • When should I use named tuples instead of normal tuples, or vice versa?
  • Are there "named lists" too? (i.开发者_运维百科e. mutable named tuples)


Named tuples are basically easy-to-create, lightweight object types. Named tuple instances can be referenced using object-like variable dereferencing or the standard tuple syntax. They can be used similarly to struct or other common record types, except that they are immutable. They were added in Python 2.6 and Python 3.0, although there is a recipe for implementation in Python 2.4.

For example, it is common to represent a point as a tuple (x, y). This leads to code like the following:

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)

Using a named tuple it becomes more readable:

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)

However, named tuples are still backwards compatible with normal tuples, so the following will still work:

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

Thus, you should use named tuples instead of tuples anywhere you think object notation will make your code more pythonic and more easily readable. I personally have started using them to represent very simple value types, particularly when passing them as parameters to functions. It makes the functions more readable, without seeing the context of the tuple packing.

Furthermore, you can also replace ordinary immutable classes that have no functions, only fields with them. You can even use your named tuple types as base classes:

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

However, as with tuples, attributes in named tuples are immutable:

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

If you want to be able change the values, you need another type. There is a handy recipe for mutable recordtypes which allow you to set new values to attributes.

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

I am not aware of any form of "named list" that lets you add new fields, however. You may just want to use a dictionary in this situation. Named tuples can be converted to dictionaries using pt1._asdict() which returns {'x': 1.0, 'y': 5.0} and can be operated upon with all the usual dictionary functions.

As already noted, you should check the documentation for more information from which these examples were constructed.


What are named tuples?

A named tuple is a tuple.

It does everything a tuple can.

But it's more than just a tuple.

It's a specific subclass of a tuple that is programmatically created to your specification, with named fields and a fixed length.

This, for example, creates a subclass of tuple, and aside from being of fixed length (in this case, three), it can be used everywhere a tuple is used without breaking. This is known as Liskov substitutability.

New in Python 3.6, we can use a class definition with typing.NamedTuple to create a namedtuple:

from typing import NamedTuple

class ANamedTuple(NamedTuple):
    """a docstring"""
    foo: int
    bar: str
    baz: list

The above is the same as collections.namedtuple, except the above additionally has type annotations and a docstring. The below is available in Python 2+:

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

This instantiates it:

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

We can inspect it and use its attributes:

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

Deeper explanation

To understand named tuples, you first need to know what a tuple is. A tuple is essentially an immutable (can't be changed in-place in memory) list.

Here's how you might use a regular tuple:

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

You can expand a tuple with iterable unpacking:

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

Named tuples are tuples that allow their elements to be accessed by name instead of just index!

You make a namedtuple like this:

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

You can also use a single string with the names separated by spaces, a slightly more readable use of the API:

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

How to use them?

You can do everything tuples can do (see above) as well as do the following:

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

A commenter asked:

In a large script or programme, where does one usually define a named tuple?

The types you create with namedtuple are basically classes you can create with easy shorthand. Treat them like classes. Define them on the module level, so that pickle and other users can find them.

The working example, on the global module level:

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

And this demonstrates the failure to lookup the definition:

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

Why/when should I use named tuples instead of normal tuples?

Use them when it improves your code to have the semantics of tuple elements expressed in your code.

You can use them instead of an object if you would otherwise use an object with unchanging data attributes and no functionality.

You can also subclass them to add functionality, for example:

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)

Why/when should I use normal tuples instead of named tuples?

It would probably be a regression to switch from using named tuples to tuples. The upfront design decision centers around whether the cost from the extra code involved is worth the improved readability when the tuple is used.

There is no extra memory used by named tuples versus tuples.

Is there any kind of "named list" (a mutable version of the named tuple)?

You're looking for either a slotted object that implements all of the functionality of a statically sized list or a subclassed list that works like a named tuple (and that somehow blocks the list from changing in size.)

A now expanded, and perhaps even Liskov substitutable, example of the first:

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

And to use, just subclass and define __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 is a factory function for making a tuple class. With that class we can create tuples that are callable by name also.

import collections

#Create a namedtuple class with names "a" "b" "c"
Row = collections.namedtuple("Row", ["a", "b", "c"])   

row = Row(a=1,b=2,c=3) #Make a namedtuple from the Row class we created

print row    #Prints: Row(a=1, b=2, c=3)
print row.a  #Prints: 1
print row[0] #Prints: 1

row = Row._make([2, 3, 4]) #Make a namedtuple from a list of values

print row   #Prints: Row(a=2, b=3, c=4)


namedtuples are a great feature, they are perfect container for data. When you have to "store" data you would use tuples or dictionaries, like:

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

or:

user = ("John", 20)

The dictionary approach is overwhelming, since dict are mutable and slower than tuples. On the other hand, the tuples are immutable and lightweight but lack readability for a great number of entries in the data fields.

namedtuples are the perfect compromise for the two approaches, the have great readability, lightweightness and immutability (plus they are polymorphic!).


named tuples allow backward compatibility with code that checks for the version like this

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

while allowing future code to be more explicit by using this syntax

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


namedtuple

is one of the easiest ways to clean up your code and make it more readable. It self-documents what is happening in the tuple. Namedtuples instances are just as memory efficient as regular tuples as they do not have per-instance dictionaries, making them faster than dictionaries.

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"

Without naming each element in the tuple, it would read like this:

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"

It is so much harder to understand what is going on in the first example. With a namedtuple, each field has a name. And you access it by name rather than position or index. Instead of p[1], we can call it p.saturation. It's easier to understand. And it looks cleaner.

Creating an instance of the namedtuple is easier than creating a dictionary.

# 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

When might you use namedtuple

  1. As just stated, the namedtuple makes understanding tuples much easier. So if you need to reference the items in the tuple, then creating them as namedtuples just makes sense.
  2. Besides being more lightweight than a dictionary, namedtuple also keeps the order unlike the dictionary.
  3. As in the example above, it is simpler to create an instance of namedtuple than dictionary. And referencing the item in the named tuple looks cleaner than a dictionary. p.hue rather than p['hue'].

The syntax

collections.namedtuple(typename, field_names[, verbose=False][, rename=False])
  • namedtuple is in the collections library.
  • typename: This is the name of the new tuple subclass.
  • field_names: A sequence of names for each field. It can be a sequence as in a list ['x', 'y', 'z'] or string x y z (without commas, just whitespace) or x, y, z.
  • rename: If rename is True, invalid fieldnames are automatically replaced with positional names. For example, ['abc', 'def', 'ghi','abc'] is converted to ['abc', '_1', 'ghi', '_3'], eliminating the keyword 'def' (since that is a reserved word for defining functions) and the duplicate fieldname 'abc'.
  • verbose: If verbose is True, the class definition is printed just before being built.

You can still access namedtuples by their position, if you so choose. p[1] == p.saturation. It still unpacks like a regular tuple.

Methods

All the regular tuple methods are supported. Ex: min(), max(), len(), in, not in, concatenation (+), index, slice, etc. And there are a few additional ones for namedtuple. Note: these all start with an underscore. _replace, _make, _asdict.

_replace Returns a new instance of the named tuple replacing specified fields with new values.

The syntax

somenamedtuple._replace(kwargs)

Example

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

Notice: The field names are not in quotes; they are keywords here. Remember: Tuples are immutable - even if they are namedtuples and have the _replace method. The _replace produces a new instance; it does not modify the original or replace the old value. You can of course save the new result to the variable. p = p._replace(hue=169)

_make

Makes a new instance from an existing sequence or iterable.

The syntax

somenamedtuple._make(iterable)

Example

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

What happened with the last one? The item inside the parenthesis should be the iterable. So a list or tuple inside the parenthesis works, but the sequence of values without enclosing as an iterable returns an error.

_asdict

Returns a new OrderedDict which maps field names to their corresponding values.

The syntax

somenamedtuple._asdict()

Example

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

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

There is also named list which is similar to named tuple but mutable https://pypi.python.org/pypi/namedlist


  • They subclass tuple, and add a layer to assign property names to the positional elements

  • Located in the collections standard library module

      from collections import namedtuple
    

'namedtuple' is a function that generates a new class that inherits from "tuple" but also provides "named properties" to access elements of the tuple.

Generating Named Tuple Classes

"namedtuple" is a class factory. It needs a few things to generate the class

  • the class name we want to use

  • A sequence of field names we want to assign, in the order of elements in the tuple. Field names can be any valid variable names except that they cannot start with an "underscore".

  • The return value of the call to "namedtuple" will be a class. We need to assign that class to a variable name in our code so we can use it to construct instances. In general, we use the same name as the name of the class that was generated.

    Coords = namedtuple('Coords', ['x', 'y'])

  • Now we can create instances of Coords:

    pt=Coords(10,20)
    
  • There are many ways we can provide the list of field names to the namedtuple function.

    • a list of strings

         namedtuple('Coords',['x','y'])
      
    • a tuple of strings

         namedtuple('Coords',('x','y'))
      
    • a single string with the field names separated by whitespace or commas

         namedtuple('Coords','x, y'])
      

Instantiating Named Tuples

After we have created a named tuple class, we can instantiate them just like an ordinary class. In fact, the __new__ method of the generated class uses the field names we provided as param names.

Coords = namedtuple('Coords', ['x', 'y'])
coord=Coords(10,20) 

Accessing Data in named tuple:

Since named tuples are just tuples, we can still handle them just like any other tuple: by index, slicing, iterate

Coords = namedtuple('Coords', ['x', 'y'])
coord=Coords(10,20)       isinstance(coord,tuple) --> True # namedtuple is subclass of tuple

x,y=coord  # Unpacking
x=coord[0] # by index
for e in coord:
    print(e)
  • Now we can also access the data using the field names just we did with the classes.

       coord.x --> 10
       coord.y --> 20
    
  • Since namedtuple is generated classes inherit from tuple, we can write like this:

     class Coord(tuple):
          ....
    
  • "coord" is a tuple, therefore immutable

"rename" keyword arg for namedtuple

Field names cannot start with an underscore

  Coords = namedtuple('Coords', ['x', '_y']) # does not work

namedtuple has a keyword-only argument, rename (defaults to False) that will automatically rename any invalid field name.

Coords = namedtuple('Coords', ['x', '_y'], rename=True)

field name "x" wont change, but "_y" will change to _1. 1 is the index of the field name.

Extracting Named Tuple values into a dictionary

Coords = namedtuple('Coords', ['x', 'y'])
coord=Coords(10,20)
coord._asdict()
   {'x': 10, 'y': 20}

Why do we use namedtuple

If you have this class:

class Stock:
    def __init__(self, symbol, year, month, day, open, high, low, close):
        self.symbol = symbol
        self.year = year
        self.month = month
        self.day = day
        self.open = open 
        self.high = high
        self.low = low
        self.close = close

Class Approach - vs - Tuple Approach

stock.symbol              stock[0]     
stock.open                stock[4]
stock.close               stock[7]
stock.high – stock.low     stock[5] – stock[6]

As you see, the tuple approach is not readable. The namedtuple function in collections allows us to create a tuple that also has names attached to each field or property. This can be handy to reference data in the tuple structure by "name" instead of just relying on position. But keep in mind, tuples are immutable so if you want mutability, stick to class

  • Since namedtuple is iterable you can use the iterable methods. For example, if you have "coords" as a class instance, you cannot look for what is the max coord. But with named-tuple, you can.


What is namedtuple ?

As the name suggests, namedtuple is a tuple with name. In standard tuple, we access the elements using the index, whereas namedtuple allows user to define name for elements. This is very handy especially processing csv (comma separated value) files and working with complex and large dataset, where the code becomes messy with the use of indices (not so pythonic).

How to use them ?

>>>from collections import namedtuple
>>>saleRecord = namedtuple('saleRecord','shopId saleDate salesAmout totalCustomers')
>>>
>>>
>>>#Assign values to a named tuple 
>>>shop11=saleRecord(11,'2015-01-01',2300,150) 
>>>shop12=saleRecord(shopId=22,saleDate="2015-01-01",saleAmout=1512,totalCustomers=125)

Reading

>>>#Reading as a namedtuple
>>>print("Shop Id =",shop12.shopId)
12
>>>print("Sale Date=",shop12.saleDate)
2015-01-01
>>>print("Sales Amount =",shop12.salesAmount)
1512
>>>print("Total Customers =",shop12.totalCustomers)
125

Interesting Scenario in CSV Processing :

from csv import reader
from collections import namedtuple

saleRecord = namedtuple('saleRecord','shopId saleDate totalSales totalCustomers')
fileHandle = open("salesRecord.csv","r")
csvFieldsList=csv.reader(fileHandle)
for fieldsList in csvFieldsList:
    shopRec = saleRecord._make(fieldsList)
    overAllSales += shopRec.totalSales;

print("Total Sales of The Retail Chain =",overAllSales)


In Python inside there is a good use of container called a named tuple, it can be used to create a definition of class and has all the features of the original tuple.

Using named tuple will be directly applied to the default class template to generate a simple class, this method allows a lot of code to improve readability and it is also very convenient when defining a class.


I think it's worth adding information about NamedTuples using type hinting:

# dependencies
from typing import NamedTuple, Optional

# definition
class MyNamedTuple(NamedTuple):
    an_attribute: str
    my_attribute: Optional[str] = None
    next_attribute: int = 1

# instantiation
my_named_tuple = MyNamedTuple("abc", "def")
# or more explicitly:
other_tuple = MyNamedTuple(an_attribute="abc", my_attribute="def")

# access
assert "abc" == my_named_tuple.an_attribute
assert 1 == other_tuple.next_attribute


Another way (a new way) to use named tuple is using NamedTuple from typing package: Type hints in namedtuple

Let's use the example of the top answer in this post to see how to use it.

(1) Before using the named tuple, the code is like this:

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

(2) Now we use the named tuple

from typing import NamedTuple

inherit the NamedTuple class and define the variable name in the new class. test is the name of the class.

class test(NamedTuple):
    x: float
    y: float

create instances from the class and assign values to them

pt1 = test(1.0, 5.0)   # x is 1.0, and y is 5.0. The order matters
pt2 = test(2.5, 1.5)

use the variables from the instances to calculate

line_length = sqrt((pt1.x - pt2.x)**2 + (pt1.y - pt2.y)**2)
print(line_length)


Try this:

collections.namedtuple()

Basically, namedtuples are easy to create, lightweight object types. They turn tuples into convenient containers for simple tasks. With namedtuples, you don’t have to use integer indices for accessing members of a tuple.

Examples:

Code 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

Code 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


Everyone else has already answered it, but I think I still have something else to add.

Namedtuple could be intuitively deemed as a shortcut to define a class.

See a cumbersome and conventional way to define a class .

class Duck:
    def __init__(self, color, weight):
        self.color = color
        self.weight = weight
red_duck = Duck('red', '10')

    In [50]: red_duck
    Out[50]: <__main__.Duck at 0x1068e4e10>
    In [51]: red_duck.color
    Out[51]: 'red'

As for namedtuple

from collections import namedtuple
Duck = namedtuple('Duck', ['color', 'weight'])
red_duck = Duck('red', '10')

In [54]: red_duck
Out[54]: Duck(color='red', weight='10')
In [55]: red_duck.color
Out[55]: 'red'
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜