动态响应解包赋值语句

在解包赋值语句中,被赋值的对象能否检查它被赋值的变量数量?


class MyObject:


    def __iter__(self):

        n = some_diabolical_hack()

        print(f"yielding {n} vals")

        return iter(["potato"]*n)

就像是:


>>> x, y = MyObject()

yielding 2 vals

>>> a, b, c = MyObject()

yielding 3 vals

在更一般的情况下,它可以内省target_list分配中使用的“形状”吗?


>>> first, *blob, d[k], (x, y), L[3:7], obj.attr, last = MyObject()

unpacking to <_ast.Tuple object at 0xcafef00d>

示例潜在用例:MagicMock()当用于修补赋值语句右侧的某个对象时,不需要预先配置固定迭代长度的改进。


LEATH
浏览 198回答 2
2回答

胡子哥哥

您可以使用回溯模块:import tracebackdef diabolically_invoke_traceback():&nbsp; &nbsp; call = traceback.extract_stack()[-2]&nbsp; &nbsp; print call[3]&nbsp; &nbsp; unpackers = call[3].split('=')[0].split(',')&nbsp; &nbsp; print len (unpackers)&nbsp; &nbsp; return range(len(unpackers))In [63]: a, b, c = diabolically_invoke_traceback()a, b, c = diabolically_invoke_traceback()3In [64]: aOut[64]: 0In [65]: bOut[65]: 1In [66]: cOut[66]: 2

慕勒3428872

也许您可以通过检查调用帧的字节码来做到这一点。如果我正确阅读了字节码指南,多重赋值由指令UNPACK_SEQUENCE或处理UNPACK_EX,具体取决于目标列表是否具有带星号的名称。这两个指令都在其参数中提供了有关目标列表形状的信息。您可以编写您的恶魔函数来爬升框架层次结构,直到找到调用框架,并检查在FUNCTION_CALL代表赋值右侧的之后发生的字节码指令。(这是假设您调用的MyObject()是语句右侧的唯一内容)。然后您可以从指令的参数中提取目标列表大小并返回它。import inspectimport disimport itertoolsdef diabolically_retrieve_target_list_size():&nbsp; &nbsp; #one f_back takes us to `get_diabolically_sized_list`'s frame. A second one takes us to the frame of the caller of `get_diabolically_sized_list`.&nbsp; &nbsp; frame = inspect.currentframe().f_back.f_back&nbsp; &nbsp; #explicitly delete frame when we're done with it to avoid reference cycles.&nbsp; &nbsp; try:&nbsp; &nbsp; &nbsp; &nbsp; #get the bytecode instruction that immediately follows the CALL_FUNCTION that is executing right now&nbsp; &nbsp; &nbsp; &nbsp; bytecode_idx = frame.f_lasti // 2&nbsp; &nbsp; &nbsp; &nbsp; unresolved_bytecodes = itertools.islice(dis.get_instructions(frame.f_code), bytecode_idx+1, bytecode_idx+3)&nbsp; &nbsp; &nbsp; &nbsp; next_bytecode = next(unresolved_bytecodes)&nbsp; &nbsp; &nbsp; &nbsp; if next_bytecode.opname == "UNPACK_SEQUENCE":&nbsp; &nbsp;#simple multiple assignment, like `a,b,c = ...`&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return next_bytecode.arg&nbsp; &nbsp; &nbsp; &nbsp; elif next_bytecode.opname == "EXTENDED_ARG":&nbsp; &nbsp; #multiple assignment with splat, like `a, *b, c = ...`&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; next_bytecode = next(unresolved_bytecodes)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if next_bytecode.opname != "UNPACK_EX":&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; raise Exception(f"Expected UNPACK_EX after EXTENDED_ARG, got {next_bytecode.opname} instead")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; args_before_star = next_bytecode.arg % 256&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; args_after_star = next_bytecode.arg >> 8&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return args_before_star + args_after_star&nbsp; &nbsp; &nbsp; &nbsp; elif next_bytecode.opname in ("STORE_FAST", "STORE_NAME"): #single assignment, like `a = ...`&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return 1&nbsp; &nbsp; &nbsp; &nbsp; else:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; raise Exception(f"Unrecognized bytecode: {frame.f_lasti} {next_bytecode.opname}")&nbsp; &nbsp; finally:&nbsp; &nbsp; &nbsp; &nbsp; del framedef get_diabolically_sized_list():&nbsp; &nbsp; count = diabolically_retrieve_target_list_size()&nbsp; &nbsp; return list(range(count))a,b,c = get_diabolically_sized_list()print(a,b,c)d,e,f,g,h,i = get_diabolically_sized_list()print(d,e,f,g,h,i)j, *k, l = get_diabolically_sized_list()print(j,k,l)x = get_diabolically_sized_list()print(x)结果:0 1 20 1 2 3 4 50 [] 1[0]
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Python