Есть ли способ проверить, назначен ли вывод функции переменной в Python?

В Python я хотел бы написать функцию, которая будет красиво выводить свои результаты на консоль, если вызывается сама по себе (в основном для интерактивного использования или для отладки). Для целей этого вопроса, допустим, он проверяет статус чего-либо. Если я позвоню просто

check_status()

Хотелось бы увидеть что-то вроде:

Pretty printer status check 0.02v
NOTE: This is so totally not written for giant robots
=================================
System operational: ... ok
Time to ion canon charge is 9m 21s
Booster rocket in AFTERBURNER state
Range check is optimal
Rocket fuel is 10h 19m 40s to depletion
Beer served is type WICKSE LAGER, chill optimal
Suggested catchphrase is 01_FIGHTING_SPIRIT_GOGOGO
Virtual ... on

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

not_robot_stat = check_status()
print not_robot_stat
>>> {'cond_op': 1, 't_canoncharge': 1342, 'stage_booster': 5, 'range_est_sigma': 0.023, 'fuel_est': 32557154, 'beer_type': 31007, 'beer_temp': 2, 'catchphrase_suggestion': 1023, 'virtual_on': 'hell yes'}

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


person Tim Lin    schedule 02.05.2009    source источник
comment
Тим, ты пробовал мой код? Он всегда будет определять, вызываете ли вы функцию из отладки, и сможет вывести диагностику, как вы запросили.   -  person Unknown    schedule 03.05.2009
comment
Что ж, я снова просматриваю требования к функциям, чтобы увидеть, нужно ли это ... но похоже, что мне нужно, чтобы это работало во ВСЕХ случаях, а не только в интерактивном режиме. Этот вопрос немного сложен, поэтому я постараюсь сделать его более последовательным и обновлю свой вопрос через несколько дней.   -  person Tim Lin    schedule 03.05.2009
comment
@ Тим, в этом случае я сделал новое решение, использующее чтение байт-кода.   -  person Unknown    schedule 03.05.2009
comment
@ Неизвестно: это на правильном пути, но смотрите комментарии ниже   -  person Tim Lin    schedule 03.05.2009


Ответы (7)


Новое решение

Это новое решение, которое определяет, когда результат функции используется для присвоения, путем проверки его собственного байт-кода. Запись байт-кода не производится, и он даже должен быть совместим с будущими версиями Python, потому что он использует модуль кода операции для определений.

import inspect, dis, opcode

def check_status():

    try:
        frame = inspect.currentframe().f_back
        next_opcode = opcode.opname[ord(frame.f_code.co_code[frame.f_lasti+3])]
        if next_opcode == "POP_TOP": 
            # or next_opcode == "RETURN_VALUE":
            # include the above line in the if statement if you consider "return check_status()" to be assignment
            print "I was not assigned"
            print "Pretty printer status check 0.02v"
            print "NOTE: This is so totally not written for giant robots"
            return
    finally:
        del frame    

    # do normal routine

    info = {'cond_op': 1, 't_canoncharge': 1342, 'stage_booster': 5}

    return info

# no assignment    
def test1():
    check_status()

# assignment
def test2():
    a = check_status()

# could be assignment (check above for options)
def test3():
    return check_status()

# assignment
def test4():
    a = []
    a.append(check_status())
    return a

Решение 1

Это старое решение, которое обнаруживает всякий раз, когда вы вызываете функцию при отладке с использованием python -i или PDB.

import inspect

def check_status():
    frame = inspect.currentframe()
    try:
        if frame.f_back.f_code.co_name == "<module>" and frame.f_back.f_code.co_filename == "<stdin>":
            print "Pretty printer status check 0.02v"
            print "NOTE: This is so totally not written for giant robots"
    finally:
        del frame

    # do regular stuff   
    return {'cond_op': 1, 't_canoncharge': 1342, 'stage_booster': 5}

def test():
    check_status()


>>> check_status()
Pretty printer status check 0.02v
NOTE: This is so totally not written for giant robots
{'cond_op': 1, 't_canoncharge': 1342, 'stage_booster': 5}

>>> a=check_status()
Pretty printer status check 0.02v
NOTE: This is so totally not written for giant robots

>>> a
{'cond_op': 1, 't_canoncharge': 1342, 'stage_booster': 5}

test()
>>>
person Unknown    schedule 02.05.2009
comment
Спасибо! Я надеялся на хорошую реализацию искажения байт-кода, если нет собственного синтаксиса, и это решает проблему! - person Tim Lin; 02.05.2009
comment
Ну, на самом деле, теперь, когда у меня было время внимательно посмотреть на это, похоже, что вы все еще не различаете, была ли функция вызвана сама по себе или вызывалась для присваивания. - person Tim Lin; 02.05.2009
comment
Контрпример: поместите код в файл blahblah.txt и запустите cat blahblah.txt | python ;-) но это просто педантичность. Дело в том, что это решение не достигает желаемых целей - красивой печати словаря или определения того, что делается с возвращаемым значением функции. (Как бы то ни было, вы кажетесь более отчаявшимся - вы не получаете голосов, умоляя их) - person David Z; 02.05.2009
comment
@ David, этот код охватывает случаи, когда вы выполняете отладку с помощью python -i и PDB, как и положено. - person Unknown; 02.05.2009
comment
Я не уверен, в чем суть вашего последнего комментария, но я по-прежнему утверждаю, что ваш первый (исходный) образец кода не выполняет то, о чем просили. Однако ваш второй пример кода более правильный. Я мог бы проголосовать за это, если бы вы были немного более любезны в своих предыдущих комментариях. - person David Z; 03.05.2009
comment
@ Дэвид, ты собираешься проголосовать против меня, потому что тебе не нравятся мои комментарии, а не обоснованность ответа? - person Unknown; 03.05.2009
comment
@Unknown: Прежде всего, спасибо за ваши усилия! Это определенно соответствует тому, что я искал. Однако в моей установке (Python 2.5) next_opcode всегда оценивается как UNPACK_SEQUENCE, независимо от того, не присвоено ли оно или присвоено 1 .... n переменным. Я не знаю, есть ли в этом проблема с моим Python. Вы, очевидно, играли с байт-кодом ... можете ли вы порекомендовать несколько ссылок, где я могу узнать больше о байт-коде Python? - person Tim Lin; 03.05.2009
comment
@ Неизвестно: о, я понял, что не так ... это была довольно надуманная логическая ошибка в коде. Я отмечу это как правильное решение. Большое спасибо! Я все же был бы признателен за некоторые рекомендации по ссылкам на байт-код. - person Tim Lin; 03.05.2009
comment
@Tim, проблема в том, что нигде не так много документации по байт-коду. Пришлось разобраться самому. Взгляните на docs.python.org/library/dis.html и полу недокументированный C: \ python25 \ Lib \ opcode.py - person Unknown; 03.05.2009
comment
@ Неизвестно есть идеи, как это сделать сейчас? Документация вроде похожа, но код не работает ... - person mariogarcc; 30.12.2018
comment
FWIW, я заставил это работать на python3.6, используя next_opcode = dis.opname[frame.f_code.co_code[frame.f_lasti + 2]]. Я не уверен, почему константа сместилась на 1 и насколько это достоверно. - person mgilson; 30.12.2018
comment
Это не работает в интерактивном режиме, потому что POP_TOP - неправильный код операции в интерактивном режиме. Интерактивное использование - это, наверное, самое важное время для его работы. Кроме того, он несовместим с новыми версиями Python из-за изменений в формате байт-кода и несовместим с вызовом из функций, написанных на C. - person user2357112 supports Monica; 30.12.2018

Однако я также хотел бы передать вывод в виде списка

Вы имеете в виду "вернуть результат в виде словаря" - будьте осторожны ;-)

Одна вещь, которую вы могли бы сделать, - это использовать способность интерпретатора Python автоматически преобразовывать в строку результат любого выражения. Для этого создайте собственный подкласс dict, который, когда его попросят преобразовать себя в строку, выполняет любое красивое форматирование, которое вы хотите. Например,

class PrettyDict(dict):
    def __str__(self):
        return '''Pretty printer status check 0.02v
NOTE: This is so totally not written for giant robots
=================================
System operational: ... %s
Time to ion canon charge is %dm %ds
Booster rocket in %s state
 (other stuff)
''' % (self.conf_op and 'ok' or 'OMGPANIC!!!',
       self.t_canoncharge / 60, self.t_canoncharge % 60, 
       BOOSTER_STATES[self.booster_charge],
       ... )

Конечно, вы могли бы придумать более красивый способ написания кода, но основная идея заключается в том, что метод __str__ создает красиво напечатанную строку, которая представляет состояние объекта и возвращает его. Затем, если вы вернете PrettyDict из своей check_status() функции, при вводе

>>> check_status()

вы бы увидели

Pretty printer status check 0.02v
NOTE: This is so totally not written for giant robots
=================================
System operational: ... ok
Time to ion canon charge is 9m 21s
Booster rocket in AFTERBURNER state
Range check is optimal
Rocket fuel is 10h 19m 40s to depletion
Beer served is type WICKSE LAGER, chill optimal
Suggested catchphrase is 01_FIGHTING_SPIRIT_GOGOGO
Virtual ... on

Единственная загвоздка в том, что

>>> not_robot_stat = check_status()
>>> print not_robot_stat

даст вам то же самое, потому что такое же преобразование в строку происходит как часть функции print. Но для использования этого в каком-то реальном приложении я сомневаюсь, что это будет иметь значение. Если вы действительно хотите видеть возвращаемое значение как чистый dict, вы можете сделать

>>> print repr(not_robot_stat)

вместо этого, и он должен показать вам

{'cond_op': 1, 't_canoncharge': 1342, 'stage_booster': 5, 'range_est_sigma': 0.023, 'fuel_est': 32557154, 'beer_type': 31007, 'beer_temp': 2, 'catchphrase_suggestion': 1023, 'virtual_on': 'hell yes'}

Дело в том, что, как говорили другие плакаты, функция в Python не может знать, что будет сделано с ее возвращаемым значением (EDIT: хорошо, может быть, будет какой-то странный байт-код -hacking way, но не делайте этого) - но вы можете обойти это в тех случаях, когда это важно.

person David Z    schedule 02.05.2009
comment
Это тоже хорошее предложение ... Я бы рассмотрел его. Спасибо! - person Tim Lin; 02.05.2009

Для этого нет варианта использования. Python присваивает все интерактивные результаты специальной переменной с именем _.

Вы можете сделать следующее в интерактивном режиме. Работает отлично. Никаких забавных дел.

>>> check_status()
{'cond_op': 1, 't_canoncharge': 1342, 'stage_booster': 5, 'range_est_sigma': 0.023, 'fuel_est': 32557154, 'beer_type': 31007, 'beer_temp': 2, 'catchphrase_suggestion': 1023, 'virtual_on': 'hell yes'}

>>> pprint.pprint( _ )
{'beer_temp': 2,
 'beer_type': 31007,
 'catchphrase_suggestion': 1023,
 'cond_op': 1,
 'fuel_est': 32557154,
 'range_est_sigma': 0.023,
 'stage_booster': 5,
 't_canoncharge': 1342,
 'virtual_on': 'hell yes'}
person S.Lott    schedule 02.05.2009

Функция не может узнать, как используется ее возвращаемое значение.

Что ж, как вы упомянули, вопиющие взломы байт-кода могли бы это сделать, но это было бы действительно сложно и, вероятно, хрупко. Пойдите более простым явным путем.

person Ned Batchelder    schedule 02.05.2009

Даже если есть способ сделать это, это плохая идея. Представьте, что вы пытаетесь отладить что-то, что ведет себя по-разному в зависимости от контекста, из которого оно вызывается. А теперь представьте, что через полгода все это похоронено в части какой-то системы, которая слишком велика, чтобы держать ее в голове сразу.

Будь проще. Явное лучше, чем неявное. Просто создайте функцию красивой печати (или используйте модуль pprint) и вызовите ее по результату. В интерактивном сеансе Pythn вы можете использовать _, чтобы получить значение последнего выражения.

person Laurence Gonsalves    schedule 02.05.2009

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

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

def check_status(return_dict=False) :
    if return_dict :
        # Return stuff here.
    # Pretty Print stuff here.

А потом...

check_status() # Pretty print
not_robot_stat = check_status(True) # Get the dict.

РЕДАКТИРОВАТЬ: Я предполагал, что вы будете печатать чаще, чем назначили. Если это не так, поменяйте местами значение по умолчанию и значение, которое вы передаете.

person sykora    schedule 02.05.2009

Единственный способ сделать то, что вы хотите, - это действительно «поиграть с байт-кодом» - другого способа восстановить эту информацию нет. Гораздо лучше, конечно, предоставить две отдельные функции: одну для получения статуса как dict, а другую для вызова первой и ее форматирования.

В качестве альтернативы (но не в отличной архитектуре) у вас может быть одна функция, которая принимает необязательный параметр, чтобы вести себя этими двумя совершенно разными способами - это не отлично, потому что функция должна иметь ОДНУ функцию, т.е. в основном делать ОДНУ вещь, а не два разных такие как эти.

person Alex Martelli    schedule 02.05.2009
comment
Я не обязательно согласен с тем, что наличие функции, которая возвращает / печатает в зависимости от переданного ей значения, - это плохая архитектура, но я думаю, что у всех есть свое мнение. - person Paolo Bergantino; 02.05.2009
comment
инженерия и архитектура - это не мнения. - person Hejazzman; 02.05.2009
comment
А другие люди не согласны с тем, что Земля круглая, настаивая на том, что она плоская: это не означает, что форма Земли зависит от мнений, это просто подтверждает, что люди вполне готовы игнорировать факты, которые противоречат их заветным предрассудкам. - person Alex Martelli; 02.05.2009
comment
Вы абсолютно правы, вера в то, что функция может возвращать или печатать вывод в зависимости от переданного ей значения, в точности сродни вере в то, что Земля плоская. Ага, это точно то же самое. Одно дело - иметь мнение, но держаться за него с такой глупостью - совсем другое чудовище ... - person Paolo Bergantino; 03.05.2009