Python - 在函数调用期间检测星号 (*)

Python - detect asterisk (*) during function call

我需要在解包时检测函数是否被星号(*)调用

问题:

我有一个特殊的 class 可以注册 __getitem__ 个键。

这些切片会将明显的键传递给 __getitem__:

object[0], object[1,1], object['depth1']['depth2']

这些不太明显:

some_iterable = [97,4,56]
some_function(*some_iterable)

因为它传递的不是真正的键,而是索引:0,1,2(因为它内部知道列表长度)


问题 来了,当我创建自己的 not subclassed class

class MyClass:
    def __init__(self,key=None):
        self.registered_key = key

    def __getitem__(self,key):
        return MyClass(key=key)

当我这样调用某个函数时会发生什么:

some_function(*MyClass())

它将创建无限循环,因为 python 不知何故不知道 MyClass 的不存在的可迭代对象有多长,因此它将永远循环,从 0 到无限递增键....

我认为这个问题没有解决方案,但是如果我能检测到 Asterisk(*) 的使用并通过一些内部逻辑解决问题,它就可以解决。我找不到任何地方,如何检测它。有什么办法吗?

可重现示例:(小心,无限循环增加内存[顺便说一句,这也很奇怪,python 不会用递归最大深度保护你]):

class MyClass:
    def __init__(self, key=None):
        self.registered_key = key

    def __getitem__(self, key):
        print(key)
        return MyClass(key=key)


def some_function(*args):
    pass


fake_iterable = MyClass()
some_function(*fake_iterable)

这是我以前没有注意到的一个奇怪的拆包怪癖。

现在我深入了解了 CPython 的细节,看看发生了什么:

  1. 一个 foo(*bar) 调用变成了一个 CALL_FUNCTION_EX 操作码。
  2. 这些已处理 here in ceval.c,其中 Py_SETREF(callargs, PySequence_Tuple(callargs)); 已完成以获取调用参数的元组。
  3. PySequence_Tuple in abstract.c 进行一些检查并调用 PyObject_GetIter(v); 以获取用于创建元组的对象的迭代器。
  4. PyObject_GetIter 检查 if (PySequence_Check(o)) return PySeqIter_New(o);.
  5. PySequence_Check看是否Py_TYPE(s)->tp_as_sequence && Py_TYPE(s)->tp_as_sequence->sq_item != NULL ...
  6. typeobject.c可以看出sq_item对应__getitem__.
  7. PySeqIter_New() returns 一个序列迭代器,这是一个明显调用 __getitem__ 直到 StopIteration 被引发的迭代器。

顺便说一句,

thats by the way also strange, that python doesnt protect you with recursion max-depth

因为这里没有递归。您实例化的新 MyClasses 尽职尽责地打包到 some_function 调用的(未来)args 数组中,不用于任何用途。


无论哪种方式,您都可以通过在 class 上声明 __iter__ 来避免这种情况。似乎拆包更喜欢尝试遍历对象。

class MyClass:
    def __init__(self,key=None):
        self.registered_key = key

    def __getitem__(self,key):
        # ...

    def __iter__(self):
        return iter([0])