Ищите идиоматический способ оценки False, если аргумент False в Python 3

У меня есть цепочка функций, каждая из которых определена в другом месте класса:

fus(roh(dah(inp)))

где inp - либо словарь, либо bool(False).

Желаемый результат состоит в том, что если inp или любая из функций, оцененных в False, False возвращается стеком функций.

Я попытался использовать тернарные операторы, но они не оцениваются правильно.

def func(inp):
    return int(inp['value']) + 1 if inp else False

выдает TypeError, bool не подлежит расшифровке, если i == False, потому что inp['value'] оценивается перед условным.

Я знаю, что могу сделать это явно:

def func(inp):
    if inp == False:
        return False
    else:
        return inp['value'] + 1

но есть тонна функций, и это будет почти четверть длины моего кода. Он также переписывает одни и те же строки кода снова и снова, что говорит мне о том, что это неправильный способ сделать что-то.

Я подозреваю, что декоратор с аргументами является ответом, но чем больше я играю с ним, тем менее я уверен в этом.

def validate_inp(inp):
    def decorator(func):
        def wrapper(*args):
             return func(inp) if inp else False
        return wrapper
    return decorator

@validate_inp(inp)
def func(inp):
    return int(inp['value']) + 1

К сожалению, вызов декоратора вызывает значение NameError, 'inp' не определен. Но я не уверен, что неправильно использую декоратор, или декоратор - неправильное решение.

Ищите комментарий, критику, предложение и/или проверку здравомыслия.


Если вы нашли это, пытаясь решить вашу собственную проблему...

Вероятно, вы хотите использовать пустые словари вместо логического False. Подставки к @chepner.

В моем приложении использование False было "нормально", не предлагало никаких преимуществ и вызывало некоторые короткие блоки кода.

Я нашел, что все проще, используя пустой словарь. Я обертываю функции, которые используют dict с декоратором, который улавливает KeyError, созданный ссылкой dict['value'], где dict пуст.

Ответы

Ответ 1

Декоратор должен выглядеть так:

def validate_inp(fun):
    def wrapper(inp):
        return fun(inp) if inp else False
    return wrapper


@validate_inp
def func(inp):
    return int(inp['value']) + 1

print(func(False))
print(func({'value': 1}))

Если вы хотите использовать свой декоратор с членом класса:

def validate_inp(fun):
    def wrapper(self, inp):
        return fun(self, inp) if inp else False
    return wrapper

class Foo(object):
    @validate_inp
    def func(self, inp):
        return int(inp['value']) + 1 if inp else False

foo = Foo()
print(foo.func(False))
print(foo.func({'value': 1}))

Ответ 2

Я попытался использовать тернарные операторы, но они не оцениваются правильно.

def func(inp):
    return int(inp['value']) + 1 if inp else False

выдает TypeError, bool не подлежит расшифровке, если i == False, потому что inp['value'] оценивается перед условным.

Это неверно - этот код работает. Кроме того, вы можете просто написать

def func(inp):
    return inp and (int(inp['value']) + 1)

Чтобы автоматически обернуть такие функции, создайте функцию, которая обертывает функцию:

def fallthrough_on_false(function):
    def inner(inp):
        return inp and function(inp)
    return inner

Это должно быть улучшено с помощью functools.wraps для переноса декораторов и имен, и, вероятно, оно должно принимать переменное количество аргументов, чтобы разрешить дополнительные расширения:

from functools import wraps

def fallthrough_on_false(function):
    @wraps(function)
    def inner(inp, *args, **kwargs):
        return inp and function(inp, *args, **kwargs)
    return inner

Ответ 3

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

>>> def validate_inp(f):
...     def wrapper(inp):
...          if not inp:
...              return False
...          return f(inp)
...     return wrapper
... 
>>> @validate_inp
... def func(inp):
...     return int(inp['value']) + 1
... 
>>> func(False)
False
>>> func({'value': 1})
2

Эти две строки

@validate_inp
def func(inp):

можно понять как это

func = validate_inp(func)

Итак, func - фактически функция wrapper, возвращаемая функцией validate_inp. С этого момента всякий раз, когда вы вызываете func, вызывается wrapper, а inp будет передаваться только функции wrapper. Затем wrapper будет решать, следует ли вызывать фактический func или нет, в зависимости от значения inp.


Если вы хотите реализовать один и тот же декоратор в классе, вам просто нужно учитывать первый параметр self в функции wrapper. Вот оно.

>>> class Test(object):
... 
...     def validate_inp(fun):
...         def wrapper(self, inp):
...             if not inp:
...                 return False
...             return fun(self, inp)
...         return wrapper
...     
...     @validate_inp
...     def func(self, inp):
...         return int(inp['value']) + 1
...     
... 
>>> Test().func(False)
False
>>> Test().func({'value': 1})
2

Так как wrapper является фактическим func, он также принимает self и inp. И когда вы вызываете функцию f (которая является фактической func), вам просто нужно передать self в качестве первого параметра.

Ответ 4

Это, вероятно, не то, что вы искали, но помогают ли они вам?

1. Используйте словарь .get вместо []. Здесь вы можете определить значение возврата. Например.

In [1548]: inp
Out[1548]: {'6': 'Hi'}

In [1549]: inp.get('5',99)
Out[1549]: 99
  1. isinstance может использоваться, чтобы проверить, является ли переменная словарем.

    In [1550]: isinstance(inp, dict)
    Out[1550]: True
    

Объединяя их (inp - тот же словарь, что и выше)

In [1554]: print "True" if isinstance(inp, dict) and len(inp.keys()) else "False"
True

Ответ 5

Один из вариантов может заключаться в определении настраиваемого исключения и небольшой оболочки:

class FalseInput(Exception): pass
def assert_not_false(inp):
    # I'll assume `inp` has to be precisely False,
    # and not something falsy like an empty dictionary.
    if inp is False:
        raise FalseInput
    return inp

Измените каждую из своих функций, чтобы поднять то же исключение вместо возврата False. Затем просто поймайте исключение один раз, в верхней части стека вызовов, но сначала заверните ввод.

try:
    x = fus(roh(dah(assert_not_false(inp))))
except FalseInput:
   x = False

Это может быть более эффективным, так как вам не обязательно будет вызывать все функции; если inp начинается с False, assert_not_false немедленно вызовет исключение, и вы перейдете прямо к предложению except.

Ответ 6

Еще один вариант: вспомогательная функция, которая принимает начальное значение и функции и применяет функции до тех пор, пока не встречается False:

def apply(x, *functions):
    for func in functions:
        if x is False:
            return x      # or break
        x = func(x)
    return x

outp = apply(inp, dah, roh, fus)