Подтвердить что ты не робот

Python: возможно ли получить доступ к возвращаемому значению внутри предложения `finally`?

У меня есть оператор return внутри предложения try:

def f():
    try:
        return whatever()
    finally:
        pass # How do I get what `whatever()` returned in here?

Можно ли получить возвращаемое значение внутри предложения finally?

Это скорее теоретический вопрос, поэтому я не ищу обходного пути, как сохранение его в переменной.

4b9b3361

Ответ 1

Нет, это не так: содержание статьи finally не зависит от механики возврата; если вы хотите увидеть, какое значение будет возвращено, вам придется делать то, что вы упомянули, и явно сохранить его где-нибудь в области видимости.

Ответ 2

Вам обязательно нужно "войти" после инструкции return?

Изменения, разрешенные перед оператором, все sys.settrace() - это все, что вам нужно.

Только после возврата:

Я думаю, что в безплатном Python вы должны это сделать. "нитки" могут быть маринованными в стеках, и должно быть возвращено значение, которое должно быть возвращено, как вершина стека значений.

В CPython я еще не нашел способ заглянуть наверху стека значений.

  • динамическое изменение frame.f_lasti не разрешено
  • динамическое изменение frame.f_code не разрешено
  • динамическое изменение frame.f_trace разрешено, но, похоже, не помогает
  • установить функцию трассировки изнутри блока finally не поймает фактическое событие возврата после того, как оператор return был "уже выполнен"
  • с статусом не улавливает возвращаемое значение
  • Я предполагаю, что вызывающий абонент игнорирует возвращаемое значение f, поэтому самоанализ или отслеживание вызывающего абонента не помогают
  • Я предполагаю, что() имеет побочные эффекты и не может быть вызван снова
  • отладчики, по крайней мере те, которые я пробовал, не получают возвращаемого значения (?); отладчики, написанные на Python, используют sys.settrace и/или последнее исключение, ни одно из них не содержит возвращаемого значения в стеке.

Конечно, все возможно с ctypes или c-level extension, здесь быстрая демонстрация:

"""
valuestack.py: Demo reading values from Python value stack
Offsets are derived for CPython-2.7.2, 64-bit, production build
"""
import ctypes, inspect, random

def id2obj(i):
    """convert CPython `id` back to object ref, by temporary pointer swap"""
    tmp = None,
    try:
        ctypes.cast(id(tmp), ctypes.POINTER(ctypes.c_ulong))[3] = i
        return tmp[0]
    finally:
        ctypes.cast(id(tmp), ctypes.POINTER(ctypes.c_ulong))[3] = id(None)

def introspect():
    """pointer on top of value stack is id of the object about to be returned
    FIXME adjust for sum(vars, locals) in introspected function
    """
    fr = inspect.stack()[1][0]
    print "caught", id2obj(ctypes.cast(id(fr), ctypes.POINTER(ctypes.c_ulong))[47])

def value():
    tmp = random.random()
    print "return", tmp
    return tmp

def foo():
    try:
        return value()
    finally:
        introspect()

if __name__ == "__main__":
    foo()

Работает с Python-2.7.2 в 64-битном режиме, поставляемом с osx:

air:~ dima$ python valuestack.py 
return 0.959725159294
caught 0.959725159294

Ответ 3

Это невозможно. Это может быть, если вы считаете гигантские халявные хаки, например, проверяете байт-код и возитесь с объектом рамки. Я не уверен, что это даже имеет смысл, так как возвращаемое значение не находится в локальном режиме, и я не думаю, что вы можете получить доступ к стеку промежуточных значений (где сохраняется возвращаемое значение) через объекты фрейма.

Возможно, вы можете использовать ctypes плюс API C, но это, вероятно, будет очень шатким, а также потребует очень хорошо знакомого с реализацией finally. Я не знаю достаточно отдаленно об этом, чтобы судить, насколько это возможно, но само собой разумеется, что это будет прямо за пределами того, что может сделать код Python.

И затем добавлена ​​проблема, что может не быть возвращаемого значения (если whatever() выдает исключение)! Я думаю, вы могли бы легко обнаружить это условие, используя sys.exc_info().

Ответ 4

def f():
    InvalidFoo = object()
    foo = InvalidFoo
    try:
        foo = whatever()
        return foo
    finally:
        if foo is not InvalidFoo:
            # look at foo