Как называются кортежи?
Именованный кортеж - это кортеж.
Он делает все, что может кортеж.
Но это больше, чем просто кортеж.
Это определенный подкласс кортежа, который программно создается по вашей спецификации с именованными полями и фиксированной длиной.
Это, например, создает подкласс кортежа, и кроме того, что он имеет фиксированную длину (в данном случае три), он может использоваться везде, где кортеж используется без разрывов. Это называется заменяемостью по Лискову.
Новое в Python 3.6 , мы можем использовать определение классаtyping.NamedTuple
для создания именованного кортежа:
from typing import NamedTuple
class ANamedTuple(NamedTuple):
"""a docstring"""
foo: int
bar: str
baz: list
Выше указано то же самое, что и ниже, за исключением того, что выше также имеет аннотации типов и строку документации. Ниже доступно в Python 2+:
>>> from collections import namedtuple
>>> class_name = 'ANamedTuple'
>>> fields = 'foo bar baz'
>>> ANamedTuple = namedtuple(class_name, fields)
Это создает его:
>>> ant = ANamedTuple(1, 'bar', [])
Мы можем проверить его и использовать его атрибуты:
>>> ant
ANamedTuple(foo=1, bar='bar', baz=[])
>>> ant.foo
1
>>> ant.bar
'bar'
>>> ant.baz.append('anything')
>>> ant.baz
['anything']
Более глубокое объяснение
Чтобы понять именованные кортежи, сначала нужно знать, что такое кортеж. Кортеж по сути является неизменным (не может быть изменен на месте в памяти) списком.
Вот как вы можете использовать обычный кортеж:
>>> student_tuple = 'Lisa', 'Simpson', 'A'
>>> student_tuple
('Lisa', 'Simpson', 'A')
>>> student_tuple[0]
'Lisa'
>>> student_tuple[1]
'Simpson'
>>> student_tuple[2]
'A'
Вы можете расширить кортеж с повторяемой распаковкой:
>>> first, last, grade = student_tuple
>>> first
'Lisa'
>>> last
'Simpson'
>>> grade
'A'
Именованные кортежи - это кортежи, которые позволяют обращаться к их элементам по имени, а не только по индексу!
Вы делаете именованный кортеж так:
>>> from collections import namedtuple
>>> Student = namedtuple('Student', ['first', 'last', 'grade'])
Вы также можете использовать одну строку с именами, разделенными пробелами, чуть более удобочитаемое использование API:
>>> Student = namedtuple('Student', 'first last grade')
Как их использовать?
Вы можете делать все, что могут делать кортежи (см. Выше), а также делать следующее:
>>> 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')
Комментатор спросил:
В большом скрипте или программе, где обычно определяется именованный кортеж?
Типы, которые вы создаете с namedtuple
, в основном являются классами, которые вы можете создавать с помощью простых сокращений. Относитесь к ним как к классам. Определите их на уровне модуля, чтобы маринованные и другие пользователи могли их найти.
Рабочий пример на уровне глобального модуля:
>>> 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')
И это демонстрирует неспособность найти определение:
>>> 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
Почему / когда я должен использовать именованные кортежи вместо обычных кортежей?
Используйте их, когда это улучшает ваш код, чтобы семантика элементов кортежа была выражена в вашем коде.
Вы можете использовать их вместо объекта, если вы иначе использовали бы объект с неизменными атрибутами данных и без функциональности.
Вы также можете разделить их на подклассы для добавления функциональности, например :
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)
Почему / когда я должен использовать нормальные кортежи вместо именованных?
Вероятно, было бы регрессией перейти от использования именованных кортежей к кортежам. Первоначальное решение по проектированию сосредотачивается вокруг того, стоит ли затраты на дополнительный код улучшенной читаемости при использовании кортежа.
Нет никакой дополнительной памяти, используемой именованными кортежами против кортежей.
Есть ли какой-либо «именованный список» (изменяемая версия именованного кортежа)?
Вы ищете объект с прорезями, который реализует все функциональные возможности списка статического размера, или подклассовый список, который работает как именованный кортеж (и который каким-то образом блокирует список от изменения размера).
А теперь расширенный и, возможно, даже подменный Лисков, пример первого:
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__)
А чтобы использовать, просто подкласс и определить __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