Сорок семь передовых методов рефакторинга для улучшения кода Python
Мы рассмотрим методы и фрагменты кода для документации Python, кодирования, тестирования, проверки и непрерывной интеграции.
Есть примеры кода Python до и после, в которых применяется каждый метод.
Техники делятся на пять категорий:
- Методы документирования
- Методы кодирования
- Методы тестирования
- Методы проверки
- Методы непрерывной интеграции (CI)
Но сначала я представлю проекты или репозитории кода, которые мы будем использовать для применения этих методов.
PyCharm — это IDE, используемая в этом блоге. Все методы, показанные здесь, также могут быть реализованы в MS Visual Basic IDE.
Что такое PHOTONAI?
PHOTONAI включает в себя sci-kit-learn и другие платформы машинного обучения (ML) или глубокого обучения (DL) с одной объединяющей парадигмой. PHOTONAI использует архитектуру научно-обучаемого Transformer
класса.
Несколько инженеров-программистов провели рефакторинг PHOTONAI для быстрых экспериментов в области машинного обучения (ML), где мы использовали образцы больших наборов данных из-за нехватки времени.
Я использую PHOTONAI не как хороший (или плохой) пример ML-фреймворка, а скорее для демонстрации рефакторинга кода.
Я привожу примеры кода до и после Python, чтобы показать преобразование PHOTONAI с использованием этой техники.
Методы документирования
Метод №1–3: Создайте новую версию и задокументируйте основные изменения.
- Создайте новую версию, a+1.0.0, если имеется значительное количество архитектурных изменений, значительное количество измененного кода и/или добавлено значительное количество нового поведения.
- Создайте новую подверсию, a.b+1.0, если есть незначительные архитектурные изменения, незначительное изменение кода и/или добавленное незначительное количество нового поведения.
- Создайте новую под-подверсию, abc+1, если добавляется небольшой объем кода и/или есть исправления ошибок.
Методы №4: Я использую PyCharm для git add
всех изменений и новых файлов проекта PHOTONAI 1.1.0.
Вы можете
git add
создать отдельный файл так же, как и проект. Щелкните левой кнопкой мыши имя файла вместо имени каталога проекта.
Показанные здесь изменения приводят к новой подрывной версии, 1.1.0* вместо 1.0.0).
Метод №5: используем Git для контроля версий и совместной работы:
git checkout -b 1.1.0
git status
Выход терминала:
On branch 1.1.0
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: photonai/base/registry/element_dictionary.py
modified: photonai/base/registry/registry.py
new file: photonai/custom_elements/CustomElements.json
new file: photonai/driver.py
modified: photonai/processing/metrics.py
modified: photonai/tests/processing_tests/metrics_test.py
new file: photonai/tests_ext/CustomElements/CustomElements.json
new file: photonai/tests_ext/base/test_hyperpipe.py
new file: photonai/tests_ext/base/test_register.py
new file: photonai/tests_ext/processing/test_metrics.py
new file: photonai/util.py
Выше приведен список всех файлов, измененных или добавленных для проекта PHOTONAI 1.1.0.
Метод №6: локально документируйте любые существенные дополнения или изменения в коде.
class Scorer(object):
"""
Transforms a string literal into a callable instance of a
particular metric
BHC 1.1.0 - support cluster scoring by add clustering
scores and type
- added METRIC_METADATA dictionary
- added ML_TYPES = ["Classification", "Regression",
"Clustering"]
- added METRIC_<>ID Postion index of metric
metadata list
- added SCORE_TYPES
- added SCORE_SIGN
"""
Метод №7: Создавайте длинные и описательные имена для констант, переменных, функций и методов класса.
Метод №8: Преобразование поведенчески неуместных комментариев в поведенчески корректные, чтобы снизить затраты на обслуживание и количество ошибок.
Метод №9: Добавьте комментарии, чтобы повысить читабельность кода.
Комментарии имеют стоимость обслуживания, а неприемлемые комментарии требуют более высокой стоимости обслуживания.
Комментарии — это часть кода. У них есть стоимость обслуживания, связанная с любым изменением поведения.
Комментарий перед изменением:
@staticmethod
def get_json(photon_package):
"""
Load JSON file in which the elements for the PHOTON submodule are stored.
The JSON files are stored in the framework folder by the name convention 'photon_package.json'
Parameters:
-----------
* 'photon_package' [str]:
The name of the photonai submodule
Returns:
--------
JSON file as dict, file path as str
"""
Комментарий после замены:
@staticmethod
def load_json(photon_package: str) -> Any:
"""
load_json Loads JSON file.
The class init PipelineElement('name',...)
stores the element metadata in a json file.
The JSON files are stored in the framework folder
by the name convention 'photon_<package>.json'.
(example:$HOME/PROJECTS/photon/photonai/base/registry/PhotonCore.json)
The file is of format
{ name-1: ['import-pkg-class-path-1', class-path-1)],
name-2: ['import-pkg-class-path-2', class-path-2)],
....}
Parameters
----------
photon_package: The name of the photonai package of element metadata
Returns
-------
[file_content, file_name]
Notes ——- if JSON file does not exist, then create blank one. “””
Метод №10: Выберите стиль строки документации и последовательно используйте его во всем проекте (в пакете, библиотеке и т. д.). Мы используем стиль NumPy, потому что он подходит для подробной документации классов, методов, функций и параметров.
Метод №11: Формат строки документации PyCharm задается из верхней последовательности ленты PyCharm|Preferences|Tools|Python Integrated Tools, как показано в следующем GIF. Макинтош отличается от Windows или Linux.
Используя стиль NumPy и исправляя любые пробелы, комментарий становится следующим:
@staticmethod
def get_json(photon_package: str) -> Any:
"""
get_json Loads JSON file.
The class init PipelineElement('name',...)
stores the element metadata in a json file.
The JSON files are stored in the framework folder
by the name convention 'photon_<package>.json'.
(example:$HOME/PROJECTS/photon/photonai/base/registry/PhotonCore.json)
The file is of format
{ name-1: ['import-pkg-class-path-1', class-path-1)],
name-2: ['import-pkg-class-path-2', class-path-2)],
....}
Parameters
----------
photon_package: The name of the photonai package
of element metadata.
Returns
-------
[file_content, file_name]
Notes ——- If JSON file does not exist, then create a blank one. “””
Метод №12: Подсказки типа сокращают документацию функции или метода.
Метод №13: PyCharm входит в шаблон строки документации NumPy. PyCharm вставляет только return
потому, что функция не имеет параметров. Если щелкнуть правой кнопкой мыши, отобразятся все разрешенные ключевые слова строки документации NumPy.
Метод №14: Применение соглашений об именах PEP-8
В приведенном ниже примере глобальная константа вводится ELEMENT_TYPE
в верхнем регистре, а переменная machine_learning_type
— в нижнем.
Globals:
ELEMENT_TYPE -> ML_TYPE
ELEMENT_DICTIONARY -> METRIC_METADATA
variables:
machine_learning_type -> element_type
Метод №15: PyCharm может запускать внешний инструмент для форматирования. Я использую черный, который форматирует .py
файл или весь проект в формат, совместимый с PEP-8. В результате все файлы форматируются одинаково. Последующий результат — повышенная оценка удобочитаемости.
Метод №16: PyCharm может менять имена в любом месте пакета. Я понимаю, что слово «рефакторинг» может быть сочтено некоторыми вредным. Тем не менее, полезно менять имя на протяжении всего проекта. Пожалуйста, не обращайте внимания на то, как PyCharm классифицирует свои функции. Используйте rename
с самого начала, чтобы вам не пришлось рефакторить код.
Метод №17: PyCharm find usage
находит все файлы в вашей ссылке на проект — например, константу, переменную, функцию, класс или метод класса.
Метод №18: Добавьте подсказку типа к каждой функции или методу класса.
Python — это язык с динамической типизацией. Python 3.5 и выше позволяет включать подсказки типов (PEP 484). Я хочу подчеркнуть подсказки слов, потому что подсказки типов не влияют на интерпретатор Python. Насколько вам известно, интерпретатор Python игнорирует подсказки типов.
Подсказки типов (примечание: не строгая проверка типов) позволяют выполнять поиск ошибок, поиск дыр в безопасности и проверку статических типов после первого прохода кода и модульного тестирования.
def is_machine_learning_type (ml_type: str) -> bool:
Метод №19: Строка документации больше не требует документирования каждого типа arg
и return
типа данных. Подпись становится самодокументируемой.
Метод №20: Увеличилась читабельность, а также информативность инструментов post-code-checking.
Метод №21: Превратите нерелевантные комментарии в релевантные.
Версия 1.0.0 (исходная)
# register new object
PhotonRegister.save("ABC1", "namespace.filename.ABC1", "Transformer")
Версия 1.1.0
# register new element
PhotonRegister.register("ABC1", "namespace.filename.ABC1", "Transformer")
Метод №22: Сохраняйте только архитектурные #todo
комментарии
Метод №23: PyCharm нашел все #Todo
для проекта PHOTONAI 1.1.0.
Метод 24: Удалите старый закомментированный код.
Версия 1.1.0 (до закомментированного кода 1.1.0):
def __post_init__(self):
if self.custom_elements_folder:
self._load_custom_folder(self.custom_elements_folder)
# base_PHOTON_REGISTRIES = ["PhotonCore", "PhotonNeuro"]
# PHOTON_REGISTRIES = ["PhotonCore", "PhotonNeuro"]
# def __init__(self, custom_elements_folder: str = None):
# if custom_elements_folder:
# self._load_custom_folder()
# else:
# self.custom_elements = None
# self.custom_elements_folder = None
# self.custom_elements_file = None
Версия 1.1.0 (после удаления закомментированного кода 1.1.0):
def __post_init__(self):
if self.custom_elements_folder:
self._load_custom_folder(self.custom_elements_folder)
Сложность строк кода (LOC) снижается за счет устранения закомментированного старого кода. Повышается показатель читабельности.
Методы кодирования
Метод №25: Не изобретайте велосипед
Прежде чем написать строку кода для своей идеи или задачи, поищите решения, написанные другими. Есть отличные репозитории. Откройте для себя свои любимые.
Метод №26: Учитесь на чужом коде.
Пакеты NumPy, pandas, Keras, fast.ai, TensorFlow, PyTorch, Kubernetes, Hadoop, PySpark и сотни других пакетов обучают передовым информационным технологиям и лучшим методам кодирования.
Воспользуйтесь преимуществом открытого исходного кода Python и других языков.
Метод №27: Log; don’t print
Метод №28: Не кодируйте глобальные структуры данных; используйте файлы параметров
Ведение журналов и файлы параметров — это предметы, которые слегка затрагиваются или вообще не изучаются на ваших физических или онлайн-классах.
О журналах и файлах параметров вы узнаете либо в школе жестких ударов, либо, если вам повезет, наблюдая за старшим инженером-программистом (или используя методы 12 и 13).
Метод №29: Создайте функцию или метод для инкапсуляции проверки значений.
В приведенном ниже примере создается метод для проверки того, находится ли значение в глобальной константе .
@staticmethod
def is_machine_learning_type(ml_type: str) -> bool:
"""
:raises
if not known machine_learning_type
:param machine_learning_type
:return: True
"""
if ml_type in Scorer.ML_TYPES:
return True
else:
logger.error(
"Specify valid ml_type to choose best config: {}".format(ml_type)
)
raise NameError(becomes
Метод №30: Создайте тип ошибки пакета
Для пакета Photoai мы создали тип ошибки Photonai
.
Метод №31: Создайте функцию повышения с типом ошибки пакета
Для типа ошибки мы создали функцию raise_PhotonaiError
.
import logging
class PhotoaiError(Exception):
pass
def raise_PhotoaiError(msg):
logger.error(msg)
raise PhotoaiError(msg)
Метод is_machine_learning_type
становится:
@staticmethod
def is_machine_learning_type(ml_type: str) -> bool:
"""
Parameters:
-----------
machine_learning_type
Returns
-----------
True
Raises
-----------
if not known machine_learning_type
"""
if ml_type in Scorer.ML_TYPES:
return True
raise_PhotoaiError(
"Specify valid ml_type:{} of [{}]".format(ml_type,
Scorer.ML_TYPES))
Примечание: raise_PhotoaiError
объединяет журнал и ошибку времени выполнения. В случае не true
останавливается и отображает стек.Как ошибка времени выполнения, она ведет себя ожидаемым образом, если передает неизвестное значение. Обратите внимание на улучшение читаемости и затрат на обслуживание.
Метод №32: Использование faulthandler
Начиная с Python 3.3, библиотека, называемая обработчиком ошибок, отображает трассировку стека вызовов в случае ошибки или сбоя сегментации Python.
Метод №33: Исключите глобальные переменные — или хотя бы попробуйте
Вы используете globals()
для перечисления всех глобальных переменных в вашем пакете.
Метод №34: Удалите все неиспользуемые локальные переменные
Обычно ваша IDE идентифицирует за вас все оборванные переменные. Если нет, пользуйтесь locals()
и пользуйтесь поиском.
Метод № 35: Примените декоратор Python 3.7+ @dataclass
перед определением класса.
@dataclass
— это дополнительная функция Python 3.7. Главной движущей силой было устранение шаблона, связанного с состоянием в def class
определении.
@dataclass
украшает def class
определение и автоматически генерирует пять методов двойного дандера: __init__()
, __repr__()
, __str__
, __eq__()
и __hash__()
.
@dataclass
практически устраняет повторяющийся шаблонный код, необходимый для определения базового класса. Обратите внимание, что все эти пять методов двойного дандера работают непосредственно с инкапсулированным состоянием класса.
class PhotonRegistry:
"""
Helper class to manage the PHOTON Element Register
...
"""
base_PHOTON_REGISTRIES = ['PhotonCore', 'PhotonNeuro']
PHOTON_REGISTRIES = ['PhotonCore', 'PhotonNeuro']
def __init__(self, custom_elements_folder: str = None):
if custom_elements_folder:
self._load_custom_folder(custom_elements_folder)
else:
self.custom_elements = None
self.custom_elements_folder = None
self.custom_elements_file = None
После @dataclass
декоратора:
@dataclass
class PhotonRegistry:
"""
Helper class to manage the PHOTON Element Register
...
"""
custom_elements_folder: str = None
custom_elements: str = None
custom_elements_file: str = None
base_PHOTON_REGISTRIES: ClassVar[List[str]] =/
["PhotonCore", "PhotonNeuro"]
PHOTON_REGISTRIES: ClassVar[List[str]] =/
["PhotonCore", "PhotonNeuro"]
def __post_init__(self):
if self.custom_elements_folder:
self._load_custom_folder(self.custom_elements_folder)
Примечание. Удобочитаемость значительно улучшилась при использовании @dataclass
with type hinting
.
Методы тестирования
Метод №36: Разработчики кода разрабатывают модульные тесты.
Метод №37: Установите инструмент pytest в PyCharm.
Метод №38: Модульные тесты имеют покрытие 80%+.
Метод №39: Интеграционные тесты должны иметь 100% покрытие всех внешних (API) функций, классов, атрибутов классов и методов классов.
Метод №40: Разработчики не проводят приемочное тестирование.
Например, мы выбрали эти модульные тесты из 24 модульных тестов в test_metrics.py
.
# 22
def test_is_machine_learning_type_bad():
with pytest.raises(PhotoaiError):
assert Scorer.is_machine_learning_type("fred") # 23 def test_is_machine_learning_type(): assert Scorer.is_machine_learning_type("Clustering")
Метод №41: После выбора инструмента тестирования PyCharm помечает отдельные тесты, которые можно запустить. Они отмечены закрашенной стрелкой на левой панели. Пример индивидуального тестирования с использованием pytest:
С помощью PyCharm вы можете запустить тестовый файл или всю тестовую папку с помощью pytest и увидеть результаты. Эталонная нижняя панель-лента (пройден тест 39/40 — 32 мс).
Метод №42: Проверка подсказок типа кода.
Типовые подсказки — это просто подсказки, которые сторонние инструменты, такие как Mypy, могут использовать для указания на потенциальные ошибки.
Метод №43: Тестовое покрытие
Метод №44: Профилирование производительности
Метод №45: Проверка безопасности
Метод №46: Надежность кода
В следующей статье подробно описаны инструменты, которые можно использовать для покрытия тестами, профилирования производительности, проверки безопасности входных данных и надежности кода.
Методы проверки
Метод №47: Проверка кода
Вывод
Я собрал эти сорок приемов рефакторинга из Python 3.5 и более ранних версий. Большинство из этих методов по-прежнему применимы к Python 3.9. Некоторые из этих методов включены в Python 3.9.