有没有办法检查函数输出是否被赋值给变量?
在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'}
那么……有没有办法在函数内部动态判断它的输出是否正在被赋值?我希望能做到这一点,而不需要传递参数,或者写一个专门的函数来处理这个问题。我在网上查了一下,似乎我得玩弄字节码。这样真的有必要吗?
8 个回答
这个没有什么实际用途。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'}
不过,我也希望它能把输出作为一个列表传递
你是说“把输出作为一个字典返回” - 小心点哦 ;-)
你可以利用Python解释器的一个特性,它可以自动把任何表达式的结果转换成字符串。为此,你可以创建一个自定义的字典子类,当它被要求转换成字符串时,就可以按照你想要的格式进行美化。例如,
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__
方法会创建一个漂亮的字符串,表示对象的状态并返回它。然后,如果你从你的check_status()
函数返回一个PrettyDict
,当你输入
>>> 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
函数中也会进行相同的字符串转换。不过在一些实际应用中,我怀疑这会有什么影响。如果你真的想看到返回值作为一个纯字典,你可以这样做
>>> 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中的函数无法知道它的返回值会被怎么使用(编辑: 好吧,也许有一些奇怪的字节码黑客方法,但别这么做) - 不过在重要的情况下,你可以找到解决办法。
新方案
这是一个新的方案,它通过检查自己的字节码来检测函数的结果是否被用于赋值。这个方案不涉及写入字节码,而且应该能与未来的Python版本兼容,因为它使用了opcode模块来定义。
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()
>>>