Задача об ошибке при валидации данных - Академия Selectel

Задача об ошибке при валидации данных

Никита Моторный Никита Моторный Старший разработчик 25 октября 2024

Будет полезна начинающим и опытным Python-разработчикам.

Изображение записи

Эту задачу подготовила подписчица Selectel Newsfeed Кристина Шараева специально для Академии Selectel. Решение написал наш коллега Никита Моторный — руководитель команды клиентских сервисов.

Условие

Вася устроился Python-разработчиком в фармацевтическую компанию. Его первый проект связан с обработкой данных, которые в будущем могут быть использованы при разработке лекарств.

Однажды Вася заметил проблему в небольшом фрагменте кода, отвечающем за валидацию данных. Оказалось, что другие разработчики при определении новой _validate_* функции иногда забывали добавить ее в список required_checks внутри функции validate_data, из-за чего новые проверки не применялись.

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

Задача

Помогите Васе исправить код:


    import pandas as pd

def _validate_size(df: pd.DataFrame) -> bool:

return not df.empty

def _validate_columns(df: pd.DataFrame) -> bool:

required_columns = {

'smiles',

'molecule_name',

}

return required_columns.issubset(set(df.columns))

def …

_validate_molecule_name(df: pd.DataFrame) -> bool:

return not df['molecule_name'].isna().any()

# many other _validate_* functions

def validate_data(df: pd.DataFrame) -> bool:

required_checks = (

_validate_size,

_validate_columns,

_validate_molecule_name,

)

for check in required_checks:

if not check(df):

return False

return True

Решение

Код выше работает, но если хочется локализовать добавление валидатора поближе к определению, можно сделать с помощью декоратора так:


    import pandas as pd
from typing import Callable


class ValidatorsCollection:
   validators = {}

   @classmethod
   def add_validator(cls, check_func: Callable):
       cls.validators[check_func.__name__] = check_func

   @classmethod
   def validate_data(cls, df: pd.DataFrame) -> list:
       errors = []
       for name, check in cls.validators.items():
           if not check(df):
               errors.append(name)

       return errors

molecules_df_validator = ValidatorsCollection.add_validator

@molecules_df_validator
def _validate_size(df: pd.DataFrame) -> bool:
   return not df.empty


@molecules_df_validator
def _validate_columns(df: pd.DataFrame) -> bool:
   required_columns = {
       'smiles',
       'molecule_name',
   }

   return required_columns.issubset(set(df.columns))

@molecules_df_validator
def _validate_molecule_name(df: pd.DataFrame) -> bool:
   return 'molecule_name' in df and not df['molecule_name'].isna().any()


resp = ValidatorsCollection.validate_data(pd.DataFrame(["1"]))
print(resp)

А если хочется использовать магию метаклассов, можно сделать так:


    from abc import ABCMeta, abstractmethod

import pandas as pd

validators = []

class ValidatorsCollcetionMeta(ABCMeta):
   def __init__(cls, name, bases, namespace):
       super().__init__(name, bases, namespace)
       validators.append(cls)

   def __str__(cls):
       return cls.__name__


class Validator(metaclass=ValidatorsCollcetionMeta):
   @staticmethod
   @abstractmethod
   def validate(df: pd.DataFrame) -> bool:
       return True


class SizeValidator(Validator):
   @staticmethod
   def validate(df: pd.DataFrame) -> bool:
       return not df.empty


class ColumnsValidator(Validator):
   @staticmethod
   def validate(df: pd.DataFrame) -> bool:

       required_columns = {
       'smiles',
       'molecule_name',
       }

       return required_columns.issubset(set(df.columns))

class MoleculeNameValidator(Validator):
   @staticmethod
   def validate(df: pd.DataFrame) -> bool:
       return 'molecule_name' in df and  not df['molecule_name'].isna().any()


errors = []

for check in validators:
   if not check.validate(pd.DataFrame()):
       errors.append(str(check))

print(errors)

В обоих примерах мы немного изменили подход к валидации: убрали их взаимозависимость в порядке исполнения и добавили индикацию, какой валидатор не отработал. Эти два пункта позволили сразу запускать весь набор валидаторов и доставлять пользователю полный список ошибок при единственном запуске валидации.

Материалы для обучения и практики