29

我正在使用 Python 3.3。我想得到一个slice对象并用它来制作一个新range对象。

它是这样的:

>>> class A:
    def __getitem__(self, item):
        if isinstance(item, slice):
            return list(range(item.start, item.stop, item.step))

>>> a = A()
>>> a[1:5:2] # works fine
[1, 3]
>>> a[1:5] # won't work :(
Traceback (most recent call last):
  File "<pyshell#18>", line 1, in <module>
    a[1:5] # won't work :(
  File "<pyshell#9>", line 4, in __getitem__
    return list(range(item.start, item.stop, item.step))
TypeError: 'NoneType' object cannot be interpreted as an integer

好吧,这里的问题很明显 -range不接受None作为值:

>>> range(1, 5, None)
Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    range(1, 5, None)
TypeError: 'NoneType' object cannot be interpreted as an integer

但是(对我来说)不明显的是解决方案。我将如何调用range,以便在每种情况下都能正常工作?我正在寻找一种很好的pythonic方式来做到这一点。

4

7 回答 7

21

有一种更简单的方法可以做到这一点(至少在 3.4 中,我目前没有 3.3,而且我在更新日志中看不到它)。

假设你的类已经有一个已知的长度,你可以切片一个该大小的范围:

>>> range(10)[1:5:2]
range(1, 5, 2)
>>> list(range(10)[1:5:2])
[1, 3]

如果您不知道先验的长度,则必须这样做:

>>> class A:
    def __getitem__(self, item):
        if isinstance(item, slice):
            return list(range(item.stop)[item])
>>> a = A()
>>> a[1:5:2]
[1, 3]
>>> a[1:5]
[1, 2, 3, 4]
于 2017-03-19T06:50:29.137 回答
16

尝试

class A:
    def __getitem__(self, item):
        ifnone = lambda a, b: b if a is None else a
        if isinstance(item, slice):
            if item.stop is None:
                # do something with itertools.count()
            else:
                return list(range(ifnone(item.start, 0), item.stop, ifnone(item.step, 1)))
        else:
            return item

如果它们是,这将重新解释.start并适当地解释。.stepNone


另一种选择可能是.indices()切片的方法。它使用条目数调用并重新解释None为适当的值,并在给定的长度参数周围包装负值:

>>> a=slice(None, None, None)
>>> a.indices(1)
(0, 1, 1)
>>> a.indices(10)
(0, 10, 1)
>>> a=slice(None, -5, None)
>>> a.indices(100)
(0, 95, 1)

这取决于你打算用负指数做什么......

于 2012-12-13T07:54:36.117 回答
10

问题:

切片由startstopstep参数组成,可以使用切片表示法或使用slice 内置的. startstop和参数中的任何(或全部)都step可以是None

# valid
sliceable[None:None:None]

# also valid
cut = slice(None, None, None)
sliceable[cut]

但是,正如原始问题中指出的那样,该range函数不接受None参数。你可以通过各种方式解决这个问题......

解决方案

使用条件逻辑:

if item.start None:
    return list(range(item.start, item.stop))
return list(range(item.start, item.stop, item.step))

...这可能会变得不必要的复杂,因为任何或所有参数都可能是None.

使用条件变量:

start = item.start if item.start is None else 0
step = item.step if item.step is None else 1
return list(range(item.start, item.stop, item.step))

...这是明确的,但有点冗长。

在语句中直接使用条件:

return list(range(item.start if item.start else 0, item.stop, item.step if item.step else 1))

...这也是不必要的冗长。

使用函数或 lambda 语句:

ifnone = lambda a, b: b if a is None else a
range(ifnone(item.start, 0), item.stop, ifnone(item.step, 1)

...这可能很难理解。

使用“或”:

return list(range(item.start or 0, item.stop or len(self), item.step or 1))

我发现使用or分配合理的默认值是最简单的。它是明确的、简单的、清晰的和简洁的。

完善实施

要完成实现,您还应该通过检查(参见int vs numbers.Integralint )来处理整数索引( ,long等)。isinstance(item, numbers.Integral)

定义__len__为允许使用len(self)默认停止值。

最后提出一个适当TypeError的无效索引(例如字符串等)。

TL;博士;

class A:
    def __len__(self):
        return 0

    def __getitem__(self, item):
        if isinstance(item, numbers.Integral):  # item is an integer
            return item
        if isinstance(item, slice):  # item is a slice
            return list(range(item.start or 0, item.stop or len(self), item.step or 1))
        else:  # invalid index type
            raise TypeError('{cls} indices must be integers or slices, not {idx}'.format(
                cls=type(self).__name__,
                idx=type(item).__name__,
            ))
于 2017-03-11T05:41:05.243 回答
4

这里的所有其他答案都完全没有。

您根本无法将 a 转换slice为 a range。没有足够的信息。

您需要知道您尝试切片的列表(或其他序列类型)的长度。一旦你有了它,你就可以在 Python 3 中使用slice.indices()轻松地创建一个范围

按照您提供的示例:

class A:
    def __init__(self, mylist):
        self.mylist = mylist

    def __getitem__(self, item):
        if isinstance(item, slice):
            mylen = len(self.mylist)
            return list(range(*item.indices(mylen)))

mylist = [1, 2, 'abc', 'def', 3, 4, None, -1]
a = A(mylist)
a[1:5]  # produces [1, 2, 3, 4]
于 2021-04-15T23:05:54.327 回答
1

我会特例item.step is None分支:

def __getitem__(self, item):
    if isinstance(item, slice):
        if item.step is None:
            return list(range(item.start, item.stop))
        return list(range(item.start, item.stop, item.step))

您将处理需要正确倒计时的范围。

于 2012-12-13T08:13:24.803 回答
0

在你的最后一个例子a[1:5]中,item.step == None你正在尝试做range(1, 5, None),这当然会导致错误。快速修复方法:

class A:
    def __getitem__(self, item):
        if isinstance(item, slice):
            return list(range(item.start, item.stop, item.step if item.step else 1)) #Changed line!

但这只是向您展示您的问题。这不是最好的方法。

于 2012-12-13T08:00:17.857 回答
0

这样的事情呢?

>>> class A:
def __getitem__(self, item):
    if isinstance(item, slice):
        return list(range(item.start, item.stop, item.step if item.step else 1))

>>> a = A()
>>> a[1:5:2] # works fine
[1, 3]
>>> a[1:5] # works as well :)
[1, 2, 3, 4]
于 2019-11-12T18:07:59.943 回答