Ошибка при тестировании возвращаемого значения декоратора

#python

#питон #python

Вопрос:

Я пытаюсь проверить свое понимание функций-оболочек, поэтому создал этот код

 def wrapper():
    print ("Wrapper")
    def wrapped():
        print ("Wrapped")
    return wrapped 

if int (wrapper()):
    print ("Returned integer")
  

Я попробовал if str(wrapper()) и if callable (wrapper()) , и все было в порядке

Я не понимаю, почему if int (wrapper()) выдает ошибку:

 Exception has occurred: TypeError
int() argument must be a string, a bytes-like object or a number, not 'function'
  

Конечно, он должен просто сказать False и вообще не содержать ошибки?

Комментарии:

1. Что вы пытаетесь сделать с if int(...) ? Для чего вы тестируете? … docs.python.org/3/library/functions.html#int

Ответ №1:

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

Случай 1: if str(wrapper())

  1. При вызове wrapper() он выводит «оболочку», а затем возвращает вызванную функцию wrapped .

  2. Таким образом, вызов str(wrapper()) эквивалентен str(wrapped) . Обратите внимание, что после этого нет круглых скобок wrapped . Это означает, что аргумент str функции является объектом функции.

    При вызове str объекта функции он возвращает строку с именем функции и положением в памяти. Проверьте этот пример:

 >>> def test():
...     pass
... 
>>> str(test)
'<function test at 0x7fa95c504668>'
>>> type(str(test))
<type 'str'>
  
  1. Поэтому if str(wrapper()) будет вычисляться if '<function wrapped at 0x7fa95c504668>' .
  2. Для всех непустых строк выполняется оператор if. Посмотрите на этот пример:
 >>> if "string":
...     print("Non empty string")
... 
Non empty string
>>> if "":
...     print("This line will not be printed")
... 
>>> 
  

Случай 2: if callable(wrapper())

  1. то же, что и раньше

  2. callable проверяет, имеет ли объект __call__ атрибут

  3. Вызов callable(wrapper()) эквивалентен callable(wrapped) . Поскольку wrapped это функция, она имеет __call__ атрибут, а выражение оценивается как True .

  4. Таким образом, if callable(wrapper()) вычисляется if True , и оператор if будет выполнен!

Случай 3: if int(wrapper())

  1. то же, что и раньше

  2. int может только преобразовывать числа и строки в целые числа.

  3. int(wrapper()) эквивалентно int(wrapped) . Итак, вы пытаетесь вызвать int с функциональным объектом!

  4. int не удается преобразовать функциональный объект в целое число и выдает сообщение со стрелкой:

 Exception has occurred: TypeError
int() argument must be a string, a bytes-like object or a number, not 'function'
  

Комментарии:

1. Спасибо за вашу помощь!

Ответ №2:

Декоратор — это просто причудливое имя для функции, которая принимает функцию в качестве аргумента и возвращает функцию. Итак, примером декоратора является:

 def wrapper(func):
    print ("Wrapper")
    def wrapped(*args, **kwargs):
        print ("Wrapped")
        func(*args, **kwargs)
    return wrapped 

@wrapper
def to_wrap():
    print("decorated function")
  

который, если вы запустите to_wrap() , вы на самом деле запускаете wrapped() с переменной func , равной to_wrap .

Итак, имея в виду эту идею, давайте посмотрим на ваш код:

 if int (wrapper()):
    print ("Returned integer")
  

то, что wrapper() возвращает, является функцией (как в вашем коде, так и в моей модификации выше), а затем вы пытаетесь преобразовать это в int и проверить его логическое значение. Это преобразование не реализовано, и, следовательно, Python вызывает TypeError, поскольку объект типа функции не может быть преобразован в целочисленный тип.

Комментарии:

1. Спасибо за вашу помощь!

Ответ №3:

Декоратор все еще необходимо вызвать. То, что у вас есть прямо сейчас, — это закрытие, которое возвращает функцию:

 def f():
    def wrapped():
         return 10
    return wrapped

x = f()
# x is the function 'wrapped'
  

Обратите внимание, что это не вызывалось wrapped , оно просто вернуло его. Вам нужно будет вызвать x , чтобы изменить это поведение:

 x()
10

# Which is implicitly calling wrapped() to return 10
  

Синтаксис декоратора позаботится об этом за вас:

 def f(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper # still returns an un-called function

@f
def x():
    return 10

x()
# 10
  

Где @f будет эффективно делать f(x()) . Поскольку вы не вызывали функцию, вы никогда не получали возвращаемый тип, поэтому int возникает TypeError .

В вашем примере явно:

 def wrapper():
    print("wrapper")
    def wrapped():
        print("wrapped")
        return "1" # you need to return a value here, otherwise it will return None
    return wrapped

int(wrapper()())
# wrapper
# wrapped
# 1
  

Обратите внимание на дополнительные круглые скобки после wrapper вызова, который вызывает wrapped . Кроме того, если вы не возвращаете значение из wrapped , то вы вернетесь None , что вызовет другое TypeError: int() argument must be a string, a bytes-like object or a number, not 'NoneType'

Комментарии:

1. Спасибо за вашу помощь!