在python中将切片转换为范围

sla*_*lum 18 python range slice python-3.x

我正在使用python3.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
Run Code Online (Sandbox Code Playgroud)

嗯,这个问题很明显 - 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
Run Code Online (Sandbox Code Playgroud)

但对我来说不明显的是解决方案.我怎么称呼range所以它会在每种情况下都有效?我正在寻找一种不错的pythonic方法来做到这一点.

谢谢 :)

Cra*_*sta 10

有一种更简单的方法(至少在3.4中,我目前没有3.3,而且我没有在更改日志中看到它).

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

>>> range(10)[1:5:2]
range(1, 5, 2)
>>> list(range(10)[1:5:2])
[1, 3]
Run Code Online (Sandbox Code Playgroud)

如果您不知道先验的长度,您将不得不这样做:

>>> 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]
Run Code Online (Sandbox Code Playgroud)

  • 在“slice(0, None, 1)”的情况下失败,并显示“TypeError: 'NoneType' object can't beterpreted as an integer”。您应该检查“无”并处理该情况。glglgl 建议使用“itertools.count()”。 (3认同)
  • 这是太棒了。我要注意的一件事是 `list(range(10)[0:20])` 不会抛出错误,这可能是有问题的! (2认同)

glg*_*lgl 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
Run Code Online (Sandbox Code Playgroud)

这将重新解释.start,.step如果它们是适当的None.


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

>>> 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)
Run Code Online (Sandbox Code Playgroud)

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

  • 你不能也这样做: ```python3 list(range(item.start or 0, item.stop, item.step or 1)) ``` (2认同)
  • @LoveToCode我认为它略有不同,也许是不好的做法。ifnone 专门测试值 none,而您的方式则替换任何被解释为 false 的内容。例如,如果您在步骤中放入 0,并且使用 ifnone,则它将保持不变,并且 range 可能会引发错误。使用 item.step 或 1,它将把 0 作为 item.step 替换为 1 (2认同)

小智 6

问题:

切片包括start,stop,和step参数,并且可以与任一被创建切片表示法或使用slice 内置的.任何(或全部)的start,stopstep参数可以None.

# valid
sliceable[None:None:None]

# also valid
cut = slice(None, None, None)
sliceable[cut]
Run Code Online (Sandbox Code Playgroud)

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

解决方案

使用条件逻辑:

if item.start None:
    return list(range(item.start, item.stop))
return list(range(item.start, item.stop, item.step))
Run Code Online (Sandbox Code Playgroud)

......由于任何或所有参数可能会变得不必要地复杂化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))
Run Code Online (Sandbox Code Playgroud)

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

有条件直接在声明中:

return list(range(item.start if item.start else 0, item.stop, item.step if item.step else 1))
Run Code Online (Sandbox Code Playgroud)

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

使用函数或lambda语句:

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

......这很难理解.

用'或':

return list(range(item.start or 0, item.stop or len(self), item.step or 1))
Run Code Online (Sandbox Code Playgroud)

我发现使用or最简单的分配合理的默认值.它明确,简单,清晰,简洁.

完成实施

为了完成你也应该处理整数索引(实施int,long通过检查等)isinstance(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__,
            ))
Run Code Online (Sandbox Code Playgroud)


Mik*_*ike 6

这里的所有其他答案都完全遗漏了它。

在一般情况下,您根本无法将 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]
Run Code Online (Sandbox Code Playgroud)