Python namedtuple as class

Understand how to use NamedTuple and Dataclass in Python

If I ask you to create a Python class for the transaction record within 10 seconds, how would you do that? Probably creating a class with __init__ is what most people would do. In this article, I want to share two alternatives in Python to construct a class: Named Tuple and Dataclass. In the end, I will compare the performance of these 3 options and give some suggestions on when to use which.

I will call the class with __init__ as “regular” Python class in this article. Please let me know if there is an official name to it. If you want to deep dive into Named Tuple and Dataclass, check out the reference links.

Each payment transaction has a sender, a receiver, a date and the amount. If I use the regular Python class, it might look something like this:

It’s very straightforward. But to be honest, it’s a lot of code, at least a lot of lines. In a use case which I’m consuming real-time transaction records from a source (e.g. Kafka), I don’t want the flexibility to modify the record. How would I achieve that in a cleaner way?

Named Tuple

Named Tuple can be a great alternative here to construct a class. Named Tuple is basically an extension of the Python built-in tuple data type. Python’s tuple is a simple data structure for grouping objects with different types. Its defining feature is being immutable.

An immutable object is an object whose state cannot be modified after it is created.

In Python, immutable types are int, float, bool, str, tuple and unicode.

However, a downside of the built-in tuple type is that it puts a lot of responsibilities on a programmer. When you access an attribute of the built-in tuple, you need to know its index. It will cause some confusion if you are developing a library which will be used by thousands of users.

Читайте также:  Css after and last child

Источник

Именованные кортежи. Пишем код на Python чище

В стандартной библиотеке питона содержится специализированный тип «namedtuple», который, кажется, не получает того внимания, которое он заслуживает. Это одна из прекрасных фич в питоне, которая скрыта с первого взгляда.

Именованные кортежи могут быть отличной альтернативой определению классов и они имеют некоторые другие интересные особенности, которые я хочу показать вам в этой статье.

Так что же такое именованный кортеж и что делает его таким специализированным? Хороший способ поразмышлять над этим — рассмотреть именованные кортежи как расширение над обычными кортежами.

Кортежи в питоне представляют собой простую структуру данных для группировки произвольных объектов. Кортежи являются неизменяемыми — они не могут быть изменены после их создания.

>>> tup = ('hello', object(), 42) >>> tup ('hello', , 42) >>> tup[2] 42 >>> tup[2] = 23 TypeError: "'tuple' object does not support item assignment"

Обратная сторона кортежей — это то, что мы можем получать данные из них используя только числовые индексы. Вы не можете дать имена отдельным элементам сохранённым в кортеже. Это может повлиять на читаемость кода.

Также, кортеж всегда является узкоспециализированной структурой. Тяжело обеспечить, чтобы два кортежа имели одни и те же номера полей и одни и те же свойства сохранённые в них. Это делает их лёгкими для знакомства со “slip-of-the-mind” багами, где легко перепутать порядок полей.

Именованные кортежи идут на выручку

Цель именованных кортежей — решить эти две проблемы.

Прежде всего, именованные кортежи являются неизменяемыми подобно обычным кортежам. Вы не можете изменять их после того, как вы что-то поместили в них.

Кроме этого, именованные кортежи это, эм. именованные кортежи. Каждый объект сохраненный в них может быть доступен через уникальный, удобный для чтения человеком, идентификатор. Это освобождает вас от запоминания целочисленных индексов или выдумывания обходных путей типа определения целочисленных констант как мнемоник для ваших индексов.

Вот как выглядит именованный кортеж:

>>> from collections import namedtuple >>> Car = namedtuple('Car' , 'color mileage')

Чтобы использовать именованный кортеж, вам нужно импортировать модуль collections . Именованные кортежи были добавлены в стандартную библиотеку в Python 2.6. В примере выше мы определили простой тип данных «Car» с двумя полями: «color» и «mileage».

Вы можете найти синтакс немного странным здесь. Почему мы передаём поля как строку закодированную с «color mileage»?

Ответ в том, что функция фабрики именованных кортежей вызывает метод split() на строки с именами полей. Таким образом, это, действительно, просто сокращение, чтобы сказать следующее:

>>> 'color mileage'.split() ['color', 'mileage'] >>> Car = namedtuple('Car', ['color', 'mileage'])

Конечно, вы также можете передать список со строками имён полей напрямую, если вы предпочитаете такой стиль. Преимущество использования списка в том, что в этом случае легко переформатировать этот код, если вам понадобится разделить его на несколько линий:

>>> Car = namedtuple('Car', [ . 'color', . 'mileage', . ])

Как бы вы ни решили, сейчас вы можете создать новые объекты «car» через фабричную функцию Car . Поведение будет такое же, как если бы вы решили определить класс Car вручную и дать ему конструктор принимающий значения «color» и «mileage»:

>>> my_car = Car('red', 3812.4) >>> my_car.color 'red' >>> my_car.mileage 3812.4

Распаковка кортежей и оператор ‘*’ для распаковки аргументов функций также работают как ожидается:

>>> color, mileage = my_car >>> print(color, mileage) red 3812.4 >>> print(*my_car) red 3812.4

Несмотря на доступ к значениям сохранённым в именованном кортеже через их идентификатор, вы всё ещё можете обращаться к ним через их индекс. Это свойство именованных кортежей может быть использовано для их распаковки в обычный кортеж:

>>> my_car[0] 'red' >>> tuple(my_car) ('red', 3812.4)

Вы даже можете получить красивое строковое отображение объектов бесплатно, что сэкономит вам время и спасёт от избыточности:

>>> my_car Car(color='red' , mileage=3812.4)

Именованные кортежи, как и обычные кортежи, являются неизменяемыми. Когда вы попытаетесь перезаписать одно из их полей, вы получите исключение AttributeError :

>>> my_car.color = 'blue' AttributeError: "can't set attribute"

Объекты именованных кортежей внутренне реализуются в питоне как обычные классы. Когда дело доходит до использованию памяти, то они так же «лучше», чем обычные классы и просто так же эффективны в использовании памяти как и обычные кортежи.

Хороший путь судить о них — считать, что именованные кортежи являются краткой формой для создания вручную эффективно работающего с памятью неизменяемого класса.

Наследование от именованных кортежей

Поскольку именованные кортежи построены на обычных классах, то вы можете добавить методы в класс, унаследованный от именованного кортежа.

>>> Car = namedtuple('Car', 'color mileage') >>> class MyCarWithMethods(Car): . def hexcolor(self): . if self.color == 'red': . return '#ff0000' . else: . return '#000000'

Сейчас мы можем создать объекты MyCarWithMethods и вызвать их метод hexcolor() так, как ожидалось:

>>> c = MyCarWithMethods('red', 1234) >>> c.hexcolor() '#ff0000'

Несмотря на то, что это может быть немного неуклюже, такая реализация может быть стоящим делом если вы хотите получить класс с неизменяемыми свойствами. Но вы легко можете прострелить себе ногу в таком случае.

Например, добавление нового неизменяемого поля является каверзной операцией из-за того как именованные кортежи устроены внутри. Более простой путь создания иерархии именованных кортежей — использование свойства ._fields базового кортежа:

>>> Car = namedtuple('Car', 'color mileage') >>> ElectricCar = namedtuple( . 'ElectricCar', Car._fields + ('charge',))

Это даёт желаемый результат:

>>> ElectricCar('red', 1234, 45.0) ElectricCar(color='red', mileage=1234, charge=45.0)

Встроенные вспомогательные методы именованного кортежа

Кроме свойства _fields каждый экземпляр именованного кортежа также предоставляет ещё несколько вспомогательных методов, которые вы можете найти полезными. Все их имена начинаются со знака подчёркивания, который говорит нам, что метод или свойство «приватное» и не является частью устоявшегося интерфейса класса или модуля.

Что касается именованных кортежей, то соглашение об именах начинающихся со знака подчёркивания здесь имеет другое значение: эти вспомогательные методы и свойства являются частью открытого интерфейса именованных кортежей. Символ подчёркивания в этих именах был использован для того, чтобы избежать коллизий имён с полями кортежа, определёнными пользователем. Так что, не стесняйтесь использовать их, если они вам понадобятся!

Я хочу показать вам несколько сценариев где вспомогательные методы именованного кортежа могут пригодиться. Давайте начнём с метода _asdict(). Он возвращает содержимое именованного кортежа в виде словаря:

>>> my_car._asdict() OrderedDict([('color', 'red'), ('mileage', 3812.4)])

Это очень здорово для избегания опечаток при создании JSON, например:

Другой полезный помощник — функция _replace(). Она создаёт поверхностную(shallow) копию кортежа и разрешает вам выборочно заменять некоторые поля:

>>> my_car._replace(color='blue') Car(color='blue', mileage=3812.4)

И, наконец, метод класса _make() может быть использован чтобы создать новый экземпляр именованного кортежа из последовательности:

>>> Car._make(['red', 999]) Car(color='red', mileage=999)

Когда стоит использовать именованные кортежи

Именованные кортежи могут быть простым способом для построения вашего кода, делая его более читаемым и обеспечивая лучшее структурирование ваших данных.

Я нахожу, например, что путь от узкоспециализированных структур данных с жёстко заданным форматом, таких как словари к именованным кортежам помогает мне выражать мои намерения более чисто. Часто когда я пытаюсь это рефакторить, то я магически достигаю лучших решений проблем, с которыми я сталкиваюсь.

Использование именованных кортежей вместо неструктурированных кортежей и словарей может также сделать жизнь моих коворкеров легче, потому что эти структуры данных помогают создавать данные понятным, самодокументирующимся(в достаточной степени) образом.

С другой стороны, я стараюсь не использовать именованные кортежи ради их пользы, если они не помогают мне писать чище, читабельнее и не делают код более лёгким в сопровождении. Слишком много хороших вещей может оказаться плохой вещью.

Однако, если вы используете их с осторожностью, то именованные кортежи, без сомнения, могут сделать ваш код на Python лучше и более выразительным.

Что нужно запомнить

  • collections.namedtuple — краткая форма для создания вручную эффективно работающего с памятью неизменяемого класса.
  • Именованные кортежи могут помочь сделать ваш код чище, обеспечивая вас более простыми в понимании структурами данных.
  • Именованные кортежи предоставляют несколько полезных вспомогательных методов которые начинаются с символа подчёркивания (_), но являются частью открытого интерфейса. Использовать их — это нормальная практика.

Источник

Оцените статью