Python切片对象和__getitem__

5

Python内部是否存在一些机制,能够将传递给__getitem__的参数区别对待,并自动将start:stop:step结构转换为切片对象呢?

以下是一个演示:

class ExampleClass(object):

  def __getitem__(self, *args):
    return args

  def __call__(self, *args):
    return args

  def randomMethod(self, *args):
    return args


a = ExampleClass()

#this works
print a[3:7:2, 1:11:2]

#syntax error on the first colon
print a.randomMethod(3:7:2, 1:11:2)
print a(3:7:2, 1:11:2)

#these work
print a.randomMethod(slice(3,7,2), slice(1,11,2))
print a(slice(3,7,2), slice(1,11,2))

它只是解释器在方括号“[]”内搜索“start:stop:step”的实例,并将其替换为“slice(start, stop, step)”吗?文档仅说明:

方括号(下标)表示法在内部使用切片对象

这是否是Python内部的代码,无法更改其行为?是否可能使其他函数使用“start:stop:step”缩写形式的切片对象?
*我看到了另一个问题:能否在方括号之外使用Python的切片符号?,但那只是使用自定义类来完成,我也可以轻松地做到。我想要的是一种方法,可以直接使用“start:stop:step”,而无需将其包装在其他任何内容中。
附注:
还有一个看起来所有方括号“[]”内的参数都封装成元组,“[*args]”似乎就像是在执行“__getitem__(args)”一样。
class ExampleClass2(object):

  def __getitem__(self, arg):
    return arg

  def __call__(self, arg):
    return arg


b = ExampleClass2()

print b["argument 1", 2:4:6,3] # ('argument 1', slice(2, 4, 6), 3)
print b(slice(3,7,2), slice(1,11,2)) # TypeError: __call__() takes exactly 2 arguments (3 given)

1
很有趣!也许 a.randomMethod([3:7:2], [1:11:2]) 或者甚至 a.randomMethod([3:7:2, 1:11:2]) 可以工作...不过根据 Python 之禅的建议,我可能更喜欢显式地将切片对象 slice(1,11,2) 传递给该方法,而不是利用一些奇怪的重载语法。 - Anentropic
不是答案,但显然Python将[表达式:表达式:表达式]转换为__getitem__(slice(...)),仅在[]内部。 - Andrey
@Anentropic 我并不想真的“使用”任何可怕的奇怪语法,我只是喜欢了解各种语言的怪异之处。就这一点而言,Python很好,因为它最初似乎隐藏了所有这些问题,这使得当你看到一个问题时更加惊讶。 - will
2个回答

5
Python语法定义了何时可以使用切片操作符:
trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
subscriptlist: subscript (',' subscript)* [',']
subscript: test | [test] ':' [test] [sliceop]
sliceop: ':' [test]
< p > test 是几乎任何表达式,但只有在 subscriptlist 内部才能使用切片操作符。因此当用于下标时,方括号 才很重要 ,但是用于列表的方括号并不能让您神奇地编写切片,也不能将切片放入仅仅位于下标内的任意表达式中。

如果您想在不对任何东西进行下标处理时使用切片,则需要编写 slice(a,b,c)


真遗憾。这个语法文件看起来像一个巨大可怕的正则表达式。 - will
1
@will 这不是正则表达式,而是BNF(http://en.wikipedia.org/wiki/Backus%E2%80%93Naur_Form)。解析器生成器将其转换为C源代码,因此您可以确信人类“可读”版本也是Python所理解的。 - Duncan
我知道这不是正则表达式,只是一个比喻。无论如何也不能是正则表达式,因为它不是规则的。 - will

2

np.lib.index_tricks 包含几个接受这种 :: 输入的“函数”,例如 np.mgridnp.r_np.s_

它们实际上是类的实例,具有 __getitem__ 定义。并且它们使用方括号“调用”。

np.s_[2::2] #  slice(2, None, 2)
np.r_[-1:1:6j, [0]*3, 5, 6]  # array([-1. , -0.6, -0.2,  0.2, ... 6. ])
mgrid[0:5,0:5]

我通常不使用它们,但它们是如何利用__getitem__的有趣示例。

np.insert是一个生成包含切片的索引元组的函数的示例。 np.apply_along也是如此:

i = zeros(nd, 'O')
...
i[axis] = slice(None, None)
...
i.put(indlist, ind)
...arr[tuple(i.tolist())]

http://docs.scipy.org/doc/numpy/reference/arrays.indexing.html上有一个相关的注释:

记住,切片元组始终可以构造为obj并用于x[obj]符号。 切片对象可以在构造中代替[start:stop:step]符号。例如,x[1:10:5,::-1]也可以表示为obj = (slice(1,10,5), slice(None,None,-1)); x[obj]。这对于构建适用于任意维度数组的通用代码非常有用。


这很有趣。我不完全确定为什么他们决定这样做,而不是创建一个最多接受三个参数的函数,你能想到他们选择这种方式的原因吗?我唯一能想到的原因是这样可以指定范围,用逗号分隔维度,但是通过传递元组也可以实现相同的效果。 - will
我认为这些函数的产生是因为在那个时候,MATLAB对于NumPy开发者的思维方式有更强的影响力。 - hpaulj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接