我如何更有效地将一串切片转换为切片对象,然后可以将这些切片对象用于切片数组和 PyTorch/NumPy 中的张量?
How do I more efficiently convert a string of slices to slice objects that can then be used to slice arrays & tensors in PyTorch / NumPy?
我如何简化这个将 PyTorch/NumPy 的切片字符串转换为切片列表对象然后可用于切片数组和张量的函数?
下面的代码可以工作,但就代码行数而言似乎效率很低。
def str_to_slice_indices(slicing_str: str):
# Convert indices to lists
indices = [
[i if i else None for i in indice_set.strip().split(":")]
for indice_set in slicing_str.strip("[]").split(",")
]
# Handle Ellipsis "..."
indices = [
... if index_slice == ["..."] else index_slice for index_slice in indices
]
# Handle "None" values
indices = [
None if index_slice == ["None"] else index_slice for index_slice in indices
]
# Handle single number values
indices = [
int(index_slice[0])
if isinstance(index_slice, list)
and len(index_slice) == 1
and index_slice[0].lstrip("-").isdigit()
else index_slice
for index_slice in indices
]
# Create indice slicing list
indices = [
slice(*[int(i) if i and i.lstrip("-").isdigit() else None for i in index_slice])
if isinstance(index_slice, list)
else index_slice
for index_slice in indices
]
return indices
运行 上面的函数带有一个涵盖各种输入类型的例子,给这个:
out = str_to_slice_indices("[None, :1, 3:4, 2, :, 2:, ...]")
print(out)
# out:
# [None, slice(None, 1, None), slice(3, 4, None), 2, slice(None, None, None), slice(2, None, None), Ellipsis]
@Michael 建议在 np.s_
上使用 eval
。
另一种证明这一点的方法是定义一个简单的 class 只接受 getitem
tuple
:
In [83]: class Foo():
...: def __getitem__(self, arg):
...: print(arg)
...:
In [84]: Foo()[None, :1, 3:4, 2, :, 2:, ...]
(None, slice(None, 1, None), slice(3, 4, None), 2, slice(None, None, None), slice(2, None, None), Ellipsis)
在正常的 Python 用法中,是解释器将 ':::' 类型的字符串转换为 slice
(和相关对象)。而且它只在索引表达式中这样做。实际上,您的代码会尝试复制解释器通常所做的工作。
我对 eval
安全问题关注不够,不知道您需要添加什么。索引语法似乎非常严格。
看起来不符合 slice
和 ellipsis
语法的字符串未更改且未评估。
In [90]: Foo()['if x is 1:print(x)']
if x is 1:print(x)
我的 Foo
和 np.s_
不尝试计算 __getitem__
传递给他们的元组。 np.s_
差不多就这么简单(代码就是找找看)。
通常 ast.literal_eval
用作 'safer' 替代 eval
,但它只处理 strings, bytes, numbers, tuples, lists, dicts, sets, booleans, and None
不需要多次迭代。示例字符串已略微扩展以测试更多案例。
def str2slices(s):
d = {True: lambda e: slice(*[int(i) if i else None for i in e.split(':')]),
'None': lambda e: None,
'...': lambda e: ...}
return [d.get(':' in e or e.strip(), lambda e: int(e))(e.strip()) for e in s[1:-1].split(',')]
str2slices('[None, :1, 3:4, 2, :, -10: ,::,:4:2, 1:10:2, -32,...]')
输出
[None,
slice(None, 1, None),
slice(3, 4, None),
2,
slice(None, None, None),
slice(-10, None, None),
slice(None, None, None),
slice(None, 4, 2),
slice(1, 10, 2),
-32,
Ellipsis]
捕获到与 OP 解决方案中相同的错误。他们不会默默地改变结果,而是抛出 ValueError
不支持的输入。
解决方案的分解
假设 string
切片和 split
函数已知。
有例子
s = '[None, :1, 3:4, 2, :, -10: ,::,:4:2, 1:10:2, -32,...]'
我们可以用
找到slices
[':' in e for e in s[1:-1].split(',')]
#[False, True, True, False, True, True, True, True, True, False, False]
使用or
short-circutting我们可以区分其他情况
[':' in e or e.strip() for e in s[1:-1].split(',')]
#['None', True, True, '2', True, True, True, True, True, '-32', '...']
这个值可以用作 dictionary
的键
d = {True: 'slice', 'None': None, '...': ...}
[d[':' in e or e.strip()] for e in s[1:-1].split(',')]
#KeyError: '2'
为了防止 KeyError
我们可以使用具有默认值的 get
方法。
d = {True: 'slice', 'None': None, '...': ...}
[d.get(':' in e or e.strip(), 'number') for e in s[1:-1].split(',')]
#[None, 'slice', 'slice', 'number', 'slice', 'slice', 'slice', 'slice', 'slice', 'number', Ellipsis]
为了处理slices
,我们需要在运行时解析额外的值。所以我们使用lambdas
作为字典值,以便能够用(e.strip())
来调用它们。最后,如果需要,我们将值转换为int
。
d = {True: lambda e: slice(*[int(i) if i else None for i in e.split(':')]),
'None': lambda e: None,
'...': lambda e: ...}
[d.get(':' in e or e.strip(), lambda e: int(e))(e.strip()) for e in s[1:-1].split(',')]
输出
[None,
slice(None, 1, None),
slice(3, 4, None),
2,
slice(None, None, None),
slice(-10, None, None),
slice(None, None, None),
slice(None, 4, 2),
slice(1, 10, 2),
-32,
Ellipsis]
我如何简化这个将 PyTorch/NumPy 的切片字符串转换为切片列表对象然后可用于切片数组和张量的函数?
下面的代码可以工作,但就代码行数而言似乎效率很低。
def str_to_slice_indices(slicing_str: str):
# Convert indices to lists
indices = [
[i if i else None for i in indice_set.strip().split(":")]
for indice_set in slicing_str.strip("[]").split(",")
]
# Handle Ellipsis "..."
indices = [
... if index_slice == ["..."] else index_slice for index_slice in indices
]
# Handle "None" values
indices = [
None if index_slice == ["None"] else index_slice for index_slice in indices
]
# Handle single number values
indices = [
int(index_slice[0])
if isinstance(index_slice, list)
and len(index_slice) == 1
and index_slice[0].lstrip("-").isdigit()
else index_slice
for index_slice in indices
]
# Create indice slicing list
indices = [
slice(*[int(i) if i and i.lstrip("-").isdigit() else None for i in index_slice])
if isinstance(index_slice, list)
else index_slice
for index_slice in indices
]
return indices
运行 上面的函数带有一个涵盖各种输入类型的例子,给这个:
out = str_to_slice_indices("[None, :1, 3:4, 2, :, 2:, ...]")
print(out)
# out:
# [None, slice(None, 1, None), slice(3, 4, None), 2, slice(None, None, None), slice(2, None, None), Ellipsis]
@Michael 建议在 np.s_
上使用 eval
。
另一种证明这一点的方法是定义一个简单的 class 只接受 getitem
tuple
:
In [83]: class Foo():
...: def __getitem__(self, arg):
...: print(arg)
...:
In [84]: Foo()[None, :1, 3:4, 2, :, 2:, ...]
(None, slice(None, 1, None), slice(3, 4, None), 2, slice(None, None, None), slice(2, None, None), Ellipsis)
在正常的 Python 用法中,是解释器将 ':::' 类型的字符串转换为 slice
(和相关对象)。而且它只在索引表达式中这样做。实际上,您的代码会尝试复制解释器通常所做的工作。
我对 eval
安全问题关注不够,不知道您需要添加什么。索引语法似乎非常严格。
看起来不符合 slice
和 ellipsis
语法的字符串未更改且未评估。
In [90]: Foo()['if x is 1:print(x)']
if x is 1:print(x)
我的 Foo
和 np.s_
不尝试计算 __getitem__
传递给他们的元组。 np.s_
差不多就这么简单(代码就是找找看)。
通常 ast.literal_eval
用作 'safer' 替代 eval
,但它只处理 strings, bytes, numbers, tuples, lists, dicts, sets, booleans, and None
不需要多次迭代。示例字符串已略微扩展以测试更多案例。
def str2slices(s):
d = {True: lambda e: slice(*[int(i) if i else None for i in e.split(':')]),
'None': lambda e: None,
'...': lambda e: ...}
return [d.get(':' in e or e.strip(), lambda e: int(e))(e.strip()) for e in s[1:-1].split(',')]
str2slices('[None, :1, 3:4, 2, :, -10: ,::,:4:2, 1:10:2, -32,...]')
输出
[None,
slice(None, 1, None),
slice(3, 4, None),
2,
slice(None, None, None),
slice(-10, None, None),
slice(None, None, None),
slice(None, 4, 2),
slice(1, 10, 2),
-32,
Ellipsis]
捕获到与 OP 解决方案中相同的错误。他们不会默默地改变结果,而是抛出 ValueError
不支持的输入。
解决方案的分解
假设 string
切片和 split
函数已知。
有例子
s = '[None, :1, 3:4, 2, :, -10: ,::,:4:2, 1:10:2, -32,...]'
我们可以用
找到slices
[':' in e for e in s[1:-1].split(',')]
#[False, True, True, False, True, True, True, True, True, False, False]
使用or
short-circutting我们可以区分其他情况
[':' in e or e.strip() for e in s[1:-1].split(',')]
#['None', True, True, '2', True, True, True, True, True, '-32', '...']
这个值可以用作 dictionary
d = {True: 'slice', 'None': None, '...': ...}
[d[':' in e or e.strip()] for e in s[1:-1].split(',')]
#KeyError: '2'
为了防止 KeyError
我们可以使用具有默认值的 get
方法。
d = {True: 'slice', 'None': None, '...': ...}
[d.get(':' in e or e.strip(), 'number') for e in s[1:-1].split(',')]
#[None, 'slice', 'slice', 'number', 'slice', 'slice', 'slice', 'slice', 'slice', 'number', Ellipsis]
为了处理slices
,我们需要在运行时解析额外的值。所以我们使用lambdas
作为字典值,以便能够用(e.strip())
来调用它们。最后,如果需要,我们将值转换为int
。
d = {True: lambda e: slice(*[int(i) if i else None for i in e.split(':')]),
'None': lambda e: None,
'...': lambda e: ...}
[d.get(':' in e or e.strip(), lambda e: int(e))(e.strip()) for e in s[1:-1].split(',')]
输出
[None,
slice(None, 1, None),
slice(3, 4, None),
2,
slice(None, None, None),
slice(-10, None, None),
slice(None, None, None),
slice(None, 4, 2),
slice(1, 10, 2),
-32,
Ellipsis]