Читаемая попытка, кроме обработки с помощью вычислений

У меня есть программа, где у меня довольно много вычислений, которые мне нужно сделать, но где вход может быть неполным (поэтому мы не всегда можем рассчитать все результаты), что само по себе прекрасно, но дает проблемы с читабельностью код:

def try_calc():
    a = {'1': 100, '2': 200, '3': 0, '4': -1, '5': None, '6': 'a'}
    try:
        a['10'] = float(a['1'] * a['2'])
    except (ZeroDivisionError, KeyError, TypeError, ValueError) as e:
        a['10'] = None
    try:
        a['11'] = float(a['1'] * a['5'])
    except (ZeroDivisionError, KeyError, TypeError, ValueError) as e:
        a['11'] = None
    try:
        a['12'] = float(a['1'] * a['6'])
    except (ZeroDivisionError, KeyError, TypeError, ValueError) as e:
        a['12'] = None
    try:
        a['13'] = float(a['1'] / a['2'])
    except (ZeroDivisionError, KeyError, TypeError, ValueError) as e:
        a['13'] = None
    try:
        a['14'] = float(a['1'] / a['3'])
    except (ZeroDivisionError, KeyError, TypeError, ValueError) as e:
        a['14'] = None
    try:
        a['15'] = float((a['1'] * a['2']) / (a['3'] * a['4']))
    except (ZeroDivisionError, KeyError, TypeError, ValueError) as e:
        a['15'] = None
    return a

In [39]: %timeit try_calc()
100000 loops, best of 3: 11 µs per loop

Итак, это хорошо работает, является высокопроизводительным, но на самом деле нечитаемым. Мы придумали еще два метода для этого. 1: Используйте специализированные функции, которые обрабатывают внутренние проблемы

import operator
def div(list_of_arguments):
    try:
        result = float(reduce(operator.div, list_of_arguments, 1))
    except (ZeroDivisionError, KeyError, TypeError, ValueError) as e:
        result = None
    return result

def mul(list_of_arguments):
    try:
        result = float(reduce(operator.mul, list_of_arguments, 1))
    except (ZeroDivisionError, KeyError, TypeError, ValueError) as e:
        result = None
    return result

def add(list_of_arguments):
    try:
        result = float(reduce(operator.add, list_of_arguments, 1))
    except (ZeroDivisionError, KeyError, TypeError, ValueError) as e:
        result = None
    return result

def try_calc2():
    a = {'1': 100, '2': 200, '3': 0, '4': -1, '5': None, '6': 'a'}
    a['10'] = mul([a['1'], a['2']])
    a['11'] = mul([a['1'], a['5']])
    a['12'] = mul([a['1'], a['6']])
    a['13'] = div([a['1'], a['2']])
    a['14'] = div([a['1'], a['3']])
    a['15'] = div([
        mul([a['1'], a['2']]), 
        mul([a['3'], a['4']])
        ])
    return a

In [40]: %timeit try_calc2()
10000 loops, best of 3: 20.3 µs per loop

Дважды, как медленный, и все же не такой читаемый, чтобы быть честным. Вариант 2: инкапсулировать внутри операторов eval

def eval_catcher(term):
    try:
        result = float(eval(term))
    except (ZeroDivisionError, KeyError, TypeError, ValueError) as e:
        result = None
    return result

def try_calc3():
    a = {'1': 100, '2': 200, '3': 0, '4': -1, '5': None, '6': 'a'}
    a['10'] = eval_catcher("a['1'] * a['2']")
    a['11'] = eval_catcher("a['1'] * a['5']")
    a['12'] = eval_catcher("a['1'] * a['6']")
    a['13'] = eval_catcher("a['1'] / a['2']")
    a['14'] = eval_catcher("a['1'] / a['3']")
    a['15'] = eval_catcher("(a['1'] * a['2']) / (a['3'] * a['4'])")
    return a

In [41]: %timeit try_calc3()
10000 loops, best of 3: 130 µs per loop

Так очень медленный (по сравнению с другими альтернативами), но в то же время самый читаемый. Я знаю, что некоторые из проблем (KeyError, ValueError) можно было бы обработать путем предварительной обработки словаря, чтобы обеспечить доступность ключей, но в любом случае все равно оставит None (TypeError) и ZeroDivisionErrors, поэтому я не вижу никакого преимущества там

Мой вопрос (ы):  - Я пропустил другие варианты?  - Я совершенно безумный, пытаясь решить его таким образом?  - Есть ли более пифонический подход?  - Как вы считаете, это лучшее решение для этого и почему?

+3
источник поделиться
3 ответа

Как насчет хранения ваших вычислений в виде лямбда? Затем вы можете пропустить все из них, используя только один блок try-except.

def try_calc():
    a = {'1': 100, '2': 200, '3': 0, '4': -1, '5': None, '6': 'a'}
    calculations = {
        '10': lambda: float(a['1'] * a['2']),
        '11': lambda: float(a['1'] * a['5']),
        '12': lambda: float(a['1'] * a['6']),
        '13': lambda: float(a['1'] / a['2']),
        '14': lambda: float(a['1'] / a['3']),
        '15': lambda: float((a['1'] * a['2']) / (a['3'] * a['4']))
    }
    for key, calculation in calculations.iteritems():
        try:
            a[key] = calculation()
        except (ZeroDivisionError, KeyError, TypeError, ValueError) as e:
            a[key] = None

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

a['3'] = float(a['1'] * a['2'])
a['5'] = float(a['3'] * a['4'])

Так как dicts неупорядочены, у вас нет гарантии, что первое уравнение будет выполнено до второго. Таким образом, a['5'] может быть рассчитано с использованием нового значения a['3'], или оно может использовать старое значение. (Это не проблема с вычислениями в вопросе, так как ключи от одного до шести никогда не назначаются, а ключи с 10 по 15 никогда не используются в расчете.)

+6
источник

Небольшое отклонение от Кевина в том, что вам не нужно предварительно хранить вычисления, но вместо этого используйте декоратор и lambda для обработки ошибок, например:

from functools import wraps

def catcher(f):
    @wraps
    def wrapper(*args):
        try:
            return f(*args)
        except (ZeroDivisionError, KeyError, TypeError, ValueError):
            return None
    return wrapper

a = {'1': 100, '2': 200, '3': 0, '4': -1, '5': None, '6': 'a'}

print catcher(lambda: a['1'] * a['5'])()

И как я уже упоминал в комментариях, вы также можете сделать общий ваш второй пример:

import operator

def reduce_op(list_of_arguments, op):
    try:
        result = float(reduce(op, list_of_arguments, 1))
    except (ZeroDivisionError, KeyError, TypeError, ValueError) as e:
        result = None
    return result

a['10'] = do_op([a['1'], a['2']], operator.mul) 
# etc...
+3
источник

У меня есть два решения, один из которых немного быстрее, чем другой.

Более читаемый:

def try_calc():
    a = {'1': 100, '2': 200, '3': 0, '4': -1, '5': None, '6': 'a'}

    fn_map = {
        '*': operator.mul,
        '/': operator.div,
    }

    def calc(x, fn, y):
        try:
            return float(fn_map[fn](a[x], a[y]))
        except (ZeroDivisionError, KeyError, TypeError, ValueError):
            return None

    a['10'] = calc('1', '*', '2')
    a['11'] = calc('1', '*', '5')
    a['12'] = calc('1', '*', '6')
    a['13'] = calc('1', '/', '2')
    a['14'] = calc('1', '/', '3')
    a['15'] = calc(calc('1', '*', '2', '/', calc('3', '*', '4'))
    return a

Чуть быстрее:

from operator import mul, div

def try_calc():
    a = {'1': 100, '2': 200, '3': 0, '4': -1, '5': None, '6': 'a'}

    def calc(x, fn, y):
        try:
            return float(fn(a[x], a[y]))
        except (ZeroDivisionError, KeyError, TypeError, ValueError):
            return None

    a['10'] = calc('1', mul, '2')
    a['11'] = calc('1', mul, '5')
    a['12'] = calc('1', mul, '6')
    a['13'] = calc('1', div, '2')
    a['14'] = calc('1', div, '3')
    a['15'] = calc(calc('1', mul, '2', div, calc('3', mul, '4'))
    return a
+1
источник

Посмотрите другие вопросы по метке или Задайте вопрос