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 的细节,看看发生了什么:
- 一个
foo(*bar)
调用变成了一个 CALL_FUNCTION_EX
操作码。
- 这些已处理 here in ceval.c,其中
Py_SETREF(callargs, PySequence_Tuple(callargs));
已完成以获取调用参数的元组。
PySequence_Tuple
in abstract.c
进行一些检查并调用 PyObject_GetIter(v);
以获取用于创建元组的对象的迭代器。
PyObject_GetIter
检查 if (PySequence_Check(o)) return PySeqIter_New(o);
.
PySequence_Check
看是否Py_TYPE(s)->tp_as_sequence && Py_TYPE(s)->tp_as_sequence->sq_item != NULL
...
- 从typeobject.c可以看出
sq_item
对应__getitem__
.
PySeqIter_New()
returns 一个序列迭代器,这是一个明显调用 __getitem__
直到 StopIteration
被引发的迭代器。
顺便说一句,
thats by the way also strange, that python doesnt protect you with recursion max-depth
因为这里没有递归。您实例化的新 MyClass
es 尽职尽责地打包到 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])
我需要在解包时检测函数是否被星号(*)调用
问题:
我有一个特殊的 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 的细节,看看发生了什么:
- 一个
foo(*bar)
调用变成了一个CALL_FUNCTION_EX
操作码。 - 这些已处理 here in ceval.c,其中
Py_SETREF(callargs, PySequence_Tuple(callargs));
已完成以获取调用参数的元组。 PySequence_Tuple
inabstract.c
进行一些检查并调用PyObject_GetIter(v);
以获取用于创建元组的对象的迭代器。PyObject_GetIter
检查if (PySequence_Check(o)) return PySeqIter_New(o);
.PySequence_Check
看是否Py_TYPE(s)->tp_as_sequence && Py_TYPE(s)->tp_as_sequence->sq_item != NULL
...- 从typeobject.c可以看出
sq_item
对应__getitem__
. PySeqIter_New()
returns 一个序列迭代器,这是一个明显调用__getitem__
直到StopIteration
被引发的迭代器。
顺便说一句,
thats by the way also strange, that python doesnt protect you with recursion max-depth
因为这里没有递归。您实例化的新 MyClass
es 尽职尽责地打包到 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])