为啥当我使用 [:] 时我的子类的 __getitem__ 和 __setitem__ 没有被调用?
Posted
技术标签:
【中文标题】为啥当我使用 [:] 时我的子类的 __getitem__ 和 __setitem__ 没有被调用?【英文标题】:Why are my subclass's __getitem__ and __setitem__ not called when I use [:]?为什么当我使用 [:] 时我的子类的 __getitem__ 和 __setitem__ 没有被调用? 【发布时间】:2018-12-20 10:09:18 【问题描述】:我正在使用 Python 2.7,我正在尝试从继承 list
的类中重载 __getitem__
和 __setitem__
。
假设我有这门课A
:
class A(list):
def __getitem__(self, key):
print "GET!"
def __setitem__(self, key, value):
print "SET!"
应使用方括号调用A.__getitem__
或A.__setitem__
。通常是这样,但是当我使用[:]
时,会调用父实现。为什么?为什么[::]
有效?
a = A([1])
a[1] # prints GET!
a["1"] # prints GET!
a[::] # prints GET!
a[slice(None)] # prints GET!
a[:] # returns the list [1]
__setitem__
也一样:
a[1] = 2 # prints SET!
a[::] = 2 # prints SET!
a[slice(None)] = 2 # prints SET!
a[:] = [2] # changes the list
【问题讨论】:
【参考方案1】:这是因为在 Python 2 [1][:]
以及带有 start 和/或 end 的一维切片(但是不是在指定 step 时),如 [1:]
、[:3]
或 [1:3]
如果已实现,则通过 __getslice__
和 __setslice__
。如果它们没有被实施,它们也将转到__getitem__
和__setitem__
)。引用文档:
请注意,这些方法 [
__*slice__
] 仅在使用带有单个冒号的单个切片时调用,并且切片方法可用。对于涉及扩展切片表示法的切片操作,或者在没有切片方法的情况下,__getitem__()
、__setitem__()
或__delitem__()
以切片对象作为参数调用。
在您的情况下,您从list
继承它们(list
实现它们)因此它在简单的切片情况下绕过您的__getitem__
和__setitem__
。
例如,您可以重写 __*slice__
方法来验证 [:]
调用是否真的到了那里:
class A(list):
def __getitem__(self, key):
print "GET!"
def __setitem__(self, key, value):
print "SET!"
def __getslice__(self, i, j):
print "GETSLICE!"
def __setslice__(self, i, j, seq):
print "SETSLICE!"
但是,只有在传入一个切片且传入的切片没有步骤时,才会调用这些函数。所以[::]
不会去那里,因为它有一个步骤(即使它是隐式的)。但[:,:]
也不会进入这些,因为它被转换为tuple(slice(None), slice(None))
,这不是一个简单的切片,而是切片的元组。如果您自己传入slice
实例,它也不会进入这些__*slice__
方法,这就是为什么[slice(None)]
即使看似等同于[:]
直接进入__*item__
而不是__*slice__
。
[1]在 Python 3 中,__*slice__
方法已被删除,因此[whatever]
索引将转到__*item__
方法。
【讨论】:
感谢您的回答,但是我不明白[::]
的情况。在键上我收到slice(None)
而不是tuple
,同样的情况也发生在直接给出slice
a[slice(None)]
[::]
隐式包含一个步骤(即使它等于默认步骤),因此它不能转到__*slice__
。以上是关于为啥当我使用 [:] 时我的子类的 __getitem__ 和 __setitem__ 没有被调用?的主要内容,如果未能解决你的问题,请参考以下文章