代码之家  ›  专栏  ›  技术社区  ›  wim

动态响应解包赋值语句

  •  1
  • wim  · 技术社区  · 7 年前

    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() 当用于修补赋值语句右侧的某个对象时,不需要预先配置固定的迭代长度。

    0 回复  |  直到 7 年前
        1
  •  3
  •   rcriii    7 年前

    您可以使用回溯模块:

    import traceback
    
    def diabolically_invoke_traceback():
        call = traceback.extract_stack()[-2]
        print call[3]
        unpackers = call[3].split('=')[0].split(',')
        print len (unpackers)
        return range(len(unpackers))
    
    In [63]: a, b, c = diabolically_invoke_traceback()
    a, b, c = diabolically_invoke_traceback()
    3
    
    In [64]: a
    Out[64]: 0
    
    In [65]: b
    Out[65]: 1
    
    In [66]: c
    Out[66]: 2
    
        2
  •  2
  •   Kevin    7 年前

    也许可以通过检查调用帧的字节码来实现这一点。如果我在读 bytecode guide 正确地说,多个赋值是由指令处理的 UNPACK_SEQUENCE UNPACK_EX ,取决于目标列表是否有星号。这两条指令都在参数中提供了有关目标列表形状的信息。

    您可以编写恶魔函数来爬升帧层次结构,直到它找到调用帧,并检查在 FUNCTION_CALL MyObject() 是声明右边唯一的东西)。然后可以从指令的参数中提取目标列表大小并返回它。

    import inspect
    import dis
    import itertools
    
    def diabolically_retrieve_target_list_size():
        #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`.
        frame = inspect.currentframe().f_back.f_back
        #explicitly delete frame when we're done with it to avoid reference cycles.
        try:
            #get the bytecode instruction that immediately follows the CALL_FUNCTION that is executing right now
            bytecode_idx = frame.f_lasti // 2
            unresolved_bytecodes = itertools.islice(dis.get_instructions(frame.f_code), bytecode_idx+1, bytecode_idx+3)
            next_bytecode = next(unresolved_bytecodes)
            if next_bytecode.opname == "UNPACK_SEQUENCE":   #simple multiple assignment, like `a,b,c = ...`
                return next_bytecode.arg
            elif next_bytecode.opname == "EXTENDED_ARG":    #multiple assignment with splat, like `a, *b, c = ...`
                next_bytecode = next(unresolved_bytecodes)
                if next_bytecode.opname != "UNPACK_EX":
                    raise Exception(f"Expected UNPACK_EX after EXTENDED_ARG, got {next_bytecode.opname} instead")
                args_before_star = next_bytecode.arg % 256
                args_after_star = next_bytecode.arg >> 8
                return args_before_star + args_after_star
            elif next_bytecode.opname in ("STORE_FAST", "STORE_NAME"): #single assignment, like `a = ...`
                return 1
            else:
                raise Exception(f"Unrecognized bytecode: {frame.f_lasti} {next_bytecode.opname}")
        finally:
            del frame
    
    def get_diabolically_sized_list():
        count = diabolically_retrieve_target_list_size()
        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 2
    0 1 2 3 4 5
    0 [] 1
    [0]