将切片转换为范围

时间:2012-12-13 07:49:27

标签: python python-3.x range slice

我正在使用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方法。

7 个答案:

答案 0 :(得分:9)

尝试

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 .step,我们会相应地重新解释None.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) 重新解释为适当的值,并在给定的长度参数周围包含负值:

{{1}}

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

答案 1 :(得分:9)

有一种更简单的方法(至少在3.4中,我目前没有3.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]

如果你不知道你必须做的先验长度:

[[l1[n] for n, l2s in enumerate(l2) if l2s == element] for element in set(l2)]

答案 2 :(得分:6)

问题:

切片由startstopstep参数组成,可以使用slice notation或使用slice built-in创建。 startstopstep参数中的任何(或全部)都可以是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来处理整数索引(longisinstance(item, numbers.Integral)等)(参见int vs numbers.Integral)。

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

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

TL; DR;

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__,
            ))

答案 3 :(得分:3)

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

您只是不能slice 转换为 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]

答案 4 :(得分: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))

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

答案 5 :(得分: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!

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

答案 6 :(得分: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]