В рамках курса «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
. Если вы хотите узнать о них больше, рекомендую почитать:
Декоратор @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
:
Заключение
На 12 примерах «до и после» я показал, как @dataclass
преобразует классы в пакете Photonai Machine Learning. Мы видели, как @dataclass
повысил производительность и читаемость кода.
Улучшение читаемости упрощает понимание кода на продакшене для всех. Вы лучше понимаете результаты, тестируете, допускаете меньше ошибок и меньше тратитесь на техническое обслуживание.
Добавление @dataclass
и подсказок типов демонстрирует, что Python продолжает расти и развиваться.
Примечание: Вы можете добавить обновленный код Photonai в свой проект из клонируемого репозитория на GitHub.
Я показал далеко не все возможности @dataclass
. Поскольку мы только добавляем кластеризацию, я продолжу документировать изменения в photonai.