В рамках курса «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.
Узнать подробнее о курсе «Python Developer. Basic».
Смотреть открытый вебинар по теме «Три кита: map(), filter() и zip()».
