Python Рефакторинг этой функции, чтобы уменьшить ее когнитивную сложность с 19 до 15 разрешенных

Я вижу это сообщение от sonarlint и пытаюсь понять, как уменьшить когнитивную сложность этой функции. Любая помощь приветствуется заранее.

import os
import json
import click
import hcl

cfn = [".json", ".template", ".yaml", ".yml"]
tf  = ["tf"]

def file_handler(dir):
    for root, dirs, files in os.walk(dir):
        for file in files:
            if file.endswith(tuple(cfn)):
                with open(os.path.join(root, file), 'r') as fin:
                    try:
                        file = fin.read()
                        if "AWSTemplateFormatVersion" in file:
                            data = json.dumps(file)
                            print(data)

                    except ValueError as e:
                        raise SystemExit(e)

            elif file.endswith(tuple(tf)):
                with open(os.path.join(root, file), 'r') as file:
                    try:
                        obj  = hcl.load(file)
                        data = json.dumps(obj)
                        print(data)
                    except ValueError as e:
                        raise SystemExit(e)
    return data

person kilomo    schedule 27.12.2017    source источник
comment
Какой инструмент метрики вы используете для получения результирующих чисел?   -  person SteveJ    schedule 27.12.2017
comment
Извлекайте функции, создавайте поиск обработчиков для типов файлов, унифицируйте обработку ошибок, удаляйте дублирование и т. д. и т. д.   -  person Peter Wood    schedule 27.12.2017
comment
Возможно, вам лучше опубликовать сообщение на Code Review.   -  person Peter Wood    schedule 27.12.2017


Ответы (3)


Функция извлечения для получения путей к файлам:

def _file_paths(directory):
    for root, dirs, filenames in os.walk(directory):
        for filename in filenames:
            file_path = os.path.join(root, filename)
            if os.path.isfile(file_path):
                yield file_path

Унифицировать обработку исключений:

from contextlib import contextmanager

@contextmanager
def _translate_error(from_error, to_error):
    try:
        yield
    except from_error as error:
        raise to_error(error)

Извлеките функции для обработки типов файлов:

def _handle_cfn(file_object):
    file_contents = file_object.read()
    if "AWSTemplateFormatVersion" in file_contents:
        data = json.dumps(file_contents)
        print(data)


def _handle_tf(file_object):
    obj  = hcl.load(file_oject)
    data = json.dumps(obj)
    print(data)

Создайте нулевой обработчик, если расширение файла не совпадает:

def _null_handler(file_object):
    pass

Сопоставьте расширения файлов с обработчиками:

_extension_handlers = {'.json': _handle_cfn,
            '.template': _handle_cfn,
            '.yaml': _handle_cfn,
            '.yml': _handle_cfn,
            '.tf': _handle_tf}

Собираем вместе:

import os
import json
import click
import hcl

def file_handler(dir):
    for file_path in _file_paths(dir):
        base, extension = os.path.splitext(file_path)
        handler = _extension_handlers.get(extension, _null_handler)
        with open(file_path) as file_object:
            with _translate_error(ValueError, SystemExit):
                handler(file_object)

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

def _handle_file(file_path):
    base, extension = os.path.splitext(file_path)
    handler = _extension_handlers.get(extension, _null_handler)
    with open(file_path) as file_object:
        with _translate_error(ValueError, SystemExit):
            handler(file_object)

Тогда ваша основная функция:

def file_handler(dir):
    for file_path in _file_paths(dir):
        _handle_file(file_path)
person Peter Wood    schedule 27.12.2017
comment
Я бы рекомендовал предпоследний file_handler (не основанный на map); map предназначен для функционального программирования и не должен использоваться для побочных эффектов (бесполезно создавать бесполезные временные list на Python 2 и вообще не работать на Python 3 без дополнительных изменений). - person ShadowRanger; 27.12.2017
comment
@ShadowRanger согласился, удалю без объяснения причин, так как это больше, чем нужно. - person Peter Wood; 27.12.2017
comment
Это сработало. def _file_paths (каталог): для корня, каталогов, имен файлов в os.walk (каталог): для имени файла в именах файлов: путь = os.path.join (корень, имя файла), если os.path.isfile (путь): выходной путь - person kilomo; 29.12.2017

Вы можете удалить один уровень отступа, используя два выражения генератора для фильтрации файлов по расширению:

def file_handler(dir):
    for root, dirs, files in os.walk(dir):
        cfn_files = (file for file in files if file.endswith(tuple(cfn)))
        tf_files = (file for file in files if file.endswith(tuple(tf)))
        for file in cfn_files:
            with open(os.path.join(root, file), 'r') as fin:
                try:
                    file = fin.read()
                    if "AWSTemplateFormatVersion" in file:
                        data = json.dumps(file)
                        print(data)
                except ValueError as e:
                    raise SystemExit(e)
        for file in tf_files:
            with open(os.path.join(root, file), 'r') as file:
                try:
                    obj  = hcl.load(file)
                    data = json.dumps(obj)
                    print(data)
                except ValueError as e:
                    raise SystemExit(e)
    return data
person Mike Müller    schedule 27.12.2017

Вам следует рассмотреть возможность использования glob для рекурсивного поиска файлов, особенно если вы знаете, какие расширения файлов вы ищете:

import glob
import json
import os

import click
import hcl

def file_handler(dir):
    for extension in cfn:
        # eg: /path/to/dir/**/*.json
        glob_search = os.path.join(dir, "**/*{}".format(extension))  
        filenames = glob.glob(glob_search, recursive=True)

        for filename in filenames:
            with open(filename, 'r') as fin:
                try:
                    file = fin.read()
                    if "AWSTemplateFormatVersion" in file:
                        data = json.dumps(file)
                        print(data)

                except ValueError as e:
                    raise SystemExit(e)

    for extension in tf:
        # eg: /path/to/dir/**/*tf
        glob_search = os.path.join(dir, "**/*{}".format(extension))
        filenames = glob.glob(glob_search, recursive=True)

        for filename in filenames:
            with open(filename, 'r') as file:
                try:
                    obj = hcl.load(file)
                    data = json.dumps(obj)
                    print(data)
                except ValueError as e:
                    raise SystemExit(e)

К вашему сведению: вопрос об использовании glob (Используйте Glob() для рекурсивно находить файлы в Python?)

person nitred    schedule 27.12.2017