12 примеров улучшения кода с помощью @dataclass

МЕНЮ


Главная страница
Поиск
Регистрация на сайте
Помощь проекту
Архив новостей

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


В рамках курса «Python Developer. Basic» подготовили для вас перевод полезного материала.

Также приглашаем всех желающих на
открытый вебинар по теме «Три кита: map(), filter() и zip()». Можно ли писать код, требующий циклов, но без циклов? Можно. Может ли он быть быстрее, чем, если бы мы использовали циклы в Python? Может. Для реализации задуманного понадобится знание слов "callback", "iterator" и "lambda". Будет сложно, но интересно. Присоединяйтесь.


Мы добавляем алгоритмы кластеризации с помощью пакетов scikit-learn, Keras и других в пакет Photonai. На 12 примерах мы покажем, как @dataclass улучшает код на Python. Для этого мы используем код из пакета Photonai для Machine Learning.

Обновитесь до Python 3.7 или более поздней версии

Декоратор @dataclass был добавлен в Python 3.7. Можно использовать Python 3.7 из Docker-образа, добавив в файл следующие команды /.bashrc_profile или /bashrc.txt.

devdir='<path-to-projects>/photon/photonai/dockerSeasons/dev/' testdir='<path-to-projects>/photon/photonai/dockerSeasons/test/' echo $devdir echo $testdir export testdir export devdir # alias updev="(cd $devdir; docker-compose up) &" alias downdev="(cd $devdir; docker-compose down) &" alias builddev="(cd $devdir; docker-compose build) &" # alias uptest="(cd $testdir; docker-compose up) & " alias downtest="(cd $testdir; docker-compose down) &" alias buildtest="cd $testdir; docker-compose build) &"

Если вы не можете найти файл /bashrc.txt создайте его самостоятельно с помощью touch/bashrc.txt. (в случае MacOS или одной из разновидностей операционных систем Linux или Unix.)

Примечание: Не забудьте указать в качестве исходника ?/.bashrc_profile или ?/bashrc.txt, когда закончите их редактировать. 

Здесь вы найдете более подробную информацию о реализации Docker, которой я пользуюсь. 

Примечание: вы можете добавить код Docker в свой проект из клонируемого репозитория на GitHub.

Добавьте подсказки типов

Python – язык с динамической типизацией. В версиях Python от 3.5 есть подсказки типов (PEP 484). Я подчеркиваю, что именно подсказки, поскольку они не влияют на работу интерпретатора Python. Насколько вам известно, интерпретатор Python вообще их игнорирует.

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

В Python 3.7 подсказки типов нужны для полей в определении класса при использовании декоратора @dataclass.

Я добавляю подсказки типов во все приведенные примеры @dataclass. Если вы хотите узнать о них больше, рекомендую почитать:

  1. https://medium.com/swlh/future-proof-your-python-code-20ef2b75e9f5

  2. https://realpython.com/python-type-checking/

  3. https://docs.python.org/3/library/typing.html

Декоратор @dataclass уменьшает шаблонность

@dataclass был добавлен в Python 3.7. Основной движущей силой было желание избавиться от шаблонности, связанной с состоянием в определении класса def

Классы могут существовать без состояния с одними лишь методами, но какой в этом смысл? Классы нужны для инкапсуляции состояния (полей данных) и методов, которые работают с полями данных. Если нет состояния, которое нужно инкапсулировать, можно преобразовать методы в функции.

Примечание: Если вы не используете pandas, можно ускорить выполнение этих функции, с помощью быстрой вставки @jit из пакета numba.

@dataclass декорирует определение класса def и автоматически генерирует 5 методов init(), repr(), str, eq(), и hash().

Примечание: он генерирует и другие методы, но об этом позже.

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

Пример коротенького класса в photon/photonai/base/hyperpipe.py, декорированный с помощью @dataclass.

### Example #1  class Data:     def __init__(self, X=None, y=None, kwargs=None):         self.X = X         self.y = y         self.kwargs = kwargs

Пример 1, после декорации =>

from dataclasses import dataclass from typing import Dict import numpy as np @dataclass class Data:     X: np.ndarray = None  # The field declaration: X     y: np.array = None    # The field declaration: y     kwargs: Dict = None   # The field declaration: kwargs

Примечание: Если тип не является частью объявления, то поле игнорируется. Используйте тип any для подстановки типа, если он меняется или во время выполнения неизвестен.

Сгенерировался ли код eq()?

### Example #2  data1 = Data() data2 = Data() data1 == data1

Пример 2, вывод =>

True

Да! А что насчет методов repr() и str?

### Example #3  print(data1) data1

Пример , вывод =>

Data(X=None, y=None, kwargs=None) Data(X=None, y=None, kwargs=None)

Да! А методы hash() и init?

Example #4  @dataclass(unsafe_hash=True) class Data:     X: np.ndarray = None     y: np.array = None     kwargs: Dict = None          data3 = Data(1,2,3) {data3:1}

Пример 4, вывод =>

{Data(X=1, y=2, kwargs=3): 1}

Да!

Примечание: У сгенерированного метода init все еще сигнатура (X, y, kwargs). Кроме того, обратите внимание, что подсказки типов были проигнорированы интерпретатором Python 3.7.

Примечание: У init(), repr(), str и eq() значение ключевого слова по умолчанию True, тогда как у hash() по умолчанию False.

Вы можете использовать inspect также, как и для любого другого экземпляра.

### Example #5  from inspect import signature print(signature(data3.__init__))

Пример 5, вывод =>

(X: numpy.ndarray = None, y: <built-in function array> = None,  kwargs: Dict = None) -> None

Круто!

Более длинный пример из photon/photonai/base/hyperpipe.py

### Example #6  class CrossValidation:      def __init__(self, inner_cv, outer_cv,                  eval_final_performance, test_size,                  calculate_metrics_per_fold,                  calculate_metrics_across_folds):         self.inner_cv = inner_cv         self.outer_cv = outer_cv         self.eval_final_performance = eval_final_performance         self.test_size = test_size         self.calculate_metrics_per_fold = calculate_metrics_per_fold         self.calculate_metrics_across_folds =             calculate_metrics_across_folds          self.outer_folds = None         self.inner_folds = dict()Example #6 Output=>

Пример 6, после декорации =>

from dataclasses import dataclass @dataclass class CrossValidation:     inner_cv: int     outer_cv: int     eval_final_performance: bool = True     test_size: float = 0.2     calculate_metrics_per_fold: bool = True     calculate_metrics_across_folds: bool = False Note:(Example #6) As any signature, keyword arguments fields with default values must be declared last. Note:(Example #6)  class CrossValidation: Readability has increased substantially by using @dataclass and type hinting.
### Example #7 cv1 = CrossValidation()

Пример 7, вывод =>

TypeError: __init__() missing 2 required positional arguments: 'inner_cv' and 'outer_cv' Note:(Example #7) inner_cv and outer_cv are positional arguments. With any signature, you declare a non-default field after a default one. (Hint: If this were allowed, inheritance from a parent class breaks.)((Why? Goggle interview question #666.))
### Example #8 cv1 = CrossValidation(1,2) cv2 = CrossValidation(1,2) cv3 = CrossValidation(3,2,test_size=0.5) print(cv1) cv3

Пример 8, вывод =>

CrossValidation(inner_cv=1, outer_cv=2, eval_final_performance=True, test_size=0.2, calculate_metrics_per_fold=True, calculate_metrics_across_folds=False) CrossValidation(inner_cv=3, outer_cv=2, eval_final_performance=True, test_size=0.5, calculate_metrics_per_fold=True, calculate_metrics_across_folds=False)
### Example #9 cv1 == cv2

Пример 9, вывод =>

True
### Example #10  cv1 == cv3

Пример 10, вывод =>

False
### Example #11 from inspect import signature print(signature(cv3.__init__)) cv3

Пример 11, вывод =>

(inner_cv: int, outer_cv: int, eval_final_performance: bool = True, test_size: float = 0.2, calculate_metrics_per_fold: bool = True, calculate_metrics_across_folds: bool = False) -> None CrossValidation(inner_cv=3, outer_cv=2, eval_final_performance=True, test_size=0.5, calculate_metrics_per_fold=True, calculate_metrics_across_folds=False) Note: (Example #11) The inspect function shows the signature of the class object while the__str__ default shows the instance state variables and their values.

Очень круто!

Упс, а что насчет:

self.outer_folds = None self.inner_folds = dict()

У нас есть переменные состояния, но они не создаются при вызове. Не волнуйтесь, @dataclass справится и с этим. Покажу в следующем разделе.

Обработка после инициализации

Существует такой метод, как post-init, который является частью определения @dataclass. Метод post_init выполняется после init, сгенерированного @dataclass. Он включает обработку после установки состояния сигнатуры. 

Мы завершаем преобразование установив оставшееся состояние CrossValidation:

### Example 12 from dataclasses import dataclass @dataclass class CrossValidation:     inner_cv: int     outer_cv: int     eval_final_performance: bool = True     test_size: float = 0.2     calculate_metrics_per_fold: bool = True     calculate_metrics_across_folds: bool = False     def __post_init__(self):         self.outer_folds = None         self.inner_folds = dict()

Источники

Здесь вы найдете отличные варианты использования декоратора @dataclass:

  1. https://realpython.com/python-data-classes/

  2. https://blog.usejournal.com/new-buzzword-in-python-is-here-dataclasses-843dd1d372a5

Заключение

На 12 примерах «до и после» я показал, как @dataclass преобразует классы в пакете Photonai Machine Learning. Мы видели, как @dataclass повысил производительность и читаемость кода. 

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

Добавление @dataclass и подсказок типов демонстрирует, что Python продолжает расти и развиваться.

Примечание: Вы можете добавить обновленный код Photonai в свой проект из клонируемого репозитория на GitHub.

Я показал далеко не все возможности @dataclass. Поскольку мы только добавляем кластеризацию, я продолжу документировать изменения в photonai.


Источник: habr.com

Комментарии: