Как разложить подпрограммы в объектно-ориентированном подходе для решения «Функция Flake8 слишком сложна (C901)»? - PullRequest
0 голосов
/ 25 января 2019

Я пишу функцию build_hierarchy_config внутри класса 'P', которая вызывает рекурсивно recursive_build_hierarchy_config.Эта функция, вызываемая в экземпляре P1, выполняет итерацию по сложной структуре dict (атрибут «config») и сравнивает все элементы с таким же атрибутом «config» из другого экземпляра «P» (называемого P2), который является родительским для P1.Логика сравнения разбита на несколько функций:

  • рекурсивный вызов recursive_build_hierarchy_config
  • сравнение кодов (перебор сложной структуры) в get_more_strict_config
  • сравнение одного элемента в cmp_config_item

Таким образом, код выглядит следующим образом:

import decimal
from typing import Optional
from attrdict import AttrDict

class Config(AttrDict):
    """Represent P config."""

    def __init__(self, some_dict):
        super(Config, self).__init__(some_dict)


class P(dict):
    """Represents P object."""

    def build_hierarchy_config(self,
                               hierarchy_config_type: str):
        """Fill the hierarchy_config or parent_hierarchy_config attribute."""

        def cmp_config_item(slave: Optional[decimal.Decimal],
                            master: Optional[decimal.Decimal],
                            cmp_func: str) -> Optional[decimal.Decimal]:
            """Compare two numbers with custom 'None' handling."""

            if cmp_func not in ['min', 'max']:
                raise Exception(
                    f'comparison function {cmp_func} not supported')
            if slave is not None and master is not None:
                return eval(cmp_func + '(' + str(slave) + ',' +
                            str(master) + ')')
            elif slave is None and master is None:
                return None
            elif slave is None:
                return master
            elif master is None:
                return slave

        def get_more_strict_config(config, parent_config) -> Config:
            """Combine configs and return most strict combinaton of values."""

            # some logic with few for and if/else
            return config

        def recursive_build_hierarchy_config(pieceId: Optional[str]):
            if pieceId is None:
                return None
            else:
                p = P()
                parent_config = recursive_build_hierarchy_config(
                    p.parentPieceId)

                return get_more_strict_config(p.config, parent_config)

        if hierarchy_config_type == 'hierarchy_config':
            self.hierarchyConfig = recursive_build_hierarchy_config(
                self.id)
        elif hierarchy_config_type == 'parent_hierarchy_config':
            self.parenthierarchyConfig = recursive_build_hierarchy_config(
                self.parentPieceId)

, но затем выполнение flake8 дает мне ошибку:

C901 'P.build_hierarchy_config' is too complex (12)

Это означает, что сложность для build_hierarchy_config суммирует сложность для всех подфункций.

Опция 1 Я легко могу это исправить, перемещая функцию cmp_config_item из build_hierarchy_config пространства имен (не уверен, что это правильная формулировка) и расположение в основной области:

import decimal
from typing import Optional

from attrdict import AttrDict


class Config(AttrDict):
    """Represent P config."""

    def __init__(self, some_dict):
        super(Config, self).__init__(some_dict)


class P(dict):
    """Represents P object."""

    def build_hierarchy_config(self,
                               hierarchy_config_type: str):
        """Fill the hierarchy_config or parent_hierarchy_config attribute."""

        def get_more_strict_config(config, parent_config) -> Config:
            """Combine configs and return most strict combinaton of values."""

            # some logic with few for and if/else
            return config

        def recursive_build_hierarchy_config(pieceId: Optional[str]):
            if pieceId is None:
                return None
            else:
                p = P()
                parent_config = recursive_build_hierarchy_config(
                    p.parentPieceId)

                return get_more_strict_config(p.config, parent_config)

        if hierarchy_config_type == 'hierarchy_config':
            self.hierarchyConfig = recursive_build_hierarchy_config(
                self.id)
        elif hierarchy_config_type == 'parent_hierarchy_config':
            self.parenthierarchyConfig = recursive_build_hierarchy_config(
                self.parentPieceId)


def cmp_config_item(slave: Optional[decimal.Decimal],
                    master: Optional[decimal.Decimal],
                    cmp_func: str) -> Optional[decimal.Decimal]:
    """Compare two numbers with custom 'None' handling."""

    if cmp_func not in ['min', 'max']:
        raise Exception(
            f'comparison function {cmp_func} not supported')
    if slave is not None and master is not None:
        return eval(cmp_func + '(' + str(slave) + ',' +
                    str(master) + ')')
    elif slave is None and master is None:
        return None
    elif slave is None:
        return master
    elif master is None:
        return slave

Теперь flake8 больше не жалуется.Но это делает функцию cmp_config_item доступной для всей области видимости модуля и делает код менее структурированным (нет никаких признаков того, что эта функция предназначена для использования в выбранной области видимости.

Опция 2 Я могу отключить это сообщение для функции, добавив # noqa: ignore=C901 после def build_hierarchy_config, как описано в другом потоке . Но это подавляет проверку самой функции build_hierarchy_config, поэтому не будет принято.

Оба решения - компромиссы. Я не специалист по ООП, поэтому возникает вопрос: существует ли какой-либо правильный / лучший способ организовать те подпрограммы, которые бы соответствовали правилам объектно-ориентированного программирования

...