【问题标题】:Dynamically responding to an unpacking assignment statement动态响应解包赋值语句
【发布时间】:2019-04-09 16:29:47
【问题描述】:

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

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

【问题讨论】:

  • 返回一个元组并让调用者处理不是更简单吗?任何时候你想在你的代码中加入一个“恶魔般的黑客”,你应该首先考虑重构它
  • 当然,恶魔般的黑客攻击几乎从未在生产质量代码中占有一席之地。但我确实认为从学术角度来看这是一个有趣的问题。即使我从不打算在我脚下开枪,我也很想看到答案:-)
  • 返回一个元组只是回避了这个问题(返回的元组应该多长时间?)。我已将问题编辑为示例用例。

标签: python iteration variable-assignment abstract-syntax-tree inspect


【解决方案1】:

你可以使用回溯模块:

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

【讨论】:

  • 简洁明了 :-) 它不适用于某些极端情况,例如带有续行的赋值(例如,a,b,c = \ 在一行上,然后是 diabolically_invoke_traceback() 在下一行),但是我的回答 also 在极端情况下不太适用,所以我无法判断:-p
【解决方案2】:

(免责声明:我不建议在生产质量代码中使用恶魔般的技术。此答案中的所有内容可能无法在与我不同的计算机、与我不同的 Python 版本或非 CPython 发行版上运行,明天早上可能就不行了。)

也许您可以通过检查调用帧的字节码来做到这一点。如果我正确阅读了bytecode guide,则多个分配由指令UNPACK_SEQUENCEUNPACK_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]

【讨论】:

  • 嗯,我突然想到这种方法只计算顶级目标列表的大小。嵌套的目标列表,例如(m, n, o), p = get_diabolically_sized_list() 将正确生成长度为 2 的列表,但不能确保列表的第一个元素可以解压缩为三个值。所以这种方法不适合捕捉复杂目标列表的“形状”。
  • 如果你继续详细说明你处理的那种赋值,你最终会重写python解析器的一个大子集,这是另一个级别的恶魔。 ;)
  • 非常正确。在 Python 聊天中,OP 想知道 ast 模块是否可能是捕获调用赋值语句的完整上下文的更好方法。我认为他正在做某事......
猜你喜欢
  • 2011-10-24
  • 2020-12-09
  • 2017-01-15
  • 1970-01-01
  • 1970-01-01
  • 2014-11-26
  • 1970-01-01
  • 2020-08-07
  • 2011-10-30
相关资源
最近更新 更多