【问题标题】:Turn slice into range将切片变为范围
【发布时间】:2012-12-01 01:46:17
【问题描述】:

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

【问题讨论】:

  • 在 Python 3 中您可以切片 range 对象以获得新的 range 对象是否有帮助?
  • 对于那些正在寻找一个简单和更一般的答案的人,来自Labrys Knossos 的答案如下:range(item.start or 0, item.stop or len(self), item.step or 1)。如果不在定义了__len__ 的类中,则根据需要替换len(self)

标签: python python-3.x range slice


【解决方案1】:

试试

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)

这取决于您打算如何处理负索引...

【讨论】:

  • 你不能也这样做吗:python3 list(range(item.start or 0, item.stop, item.step or 1))
  • @LoveToCode 我认为它略有不同,可能是不好的做法。 ifnone 专门测试值 none,而您的方式替换任何被解释为 false 的内容。例如,如果您在步骤中输入 0,并且使用 ifnone,它将保持不变,并且范围可能会引发错误。使用 item.step 或 1,它将 0 替换为 item.step 1
  • slice 的.indices() 方法正是我想要的。谢谢!
【解决方案2】:

在您的最后一个示例中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!

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

【讨论】:

  • 如果stop 低于start 怎么办?那么步骤应该是-1!
  • @MartijnPieters - 那么它不会像预期的那样返回任何东西,就像列表中的切片一样。
  • @slallum:但这是一个范围。列表可能会将切片解释为空列表,但在低于 start 的范围内停止会导致自动 -1 步骤。
  • @MartijnPieters - 你确定吗?我有 python3.3 和 python2.7,它们都 list(range(7,2)) 返回一个空列表..
  • @slallum:天哪,不知道我是怎么想到它会自动处理正确的step。显示我使用负值范围的频率..
【解决方案3】:

我会特例 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))

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

【讨论】:

  • 这很酷,但是item.start 也可以是None,而且我认为代码中太多的ifs 会使它看起来很丑,所以我在indices 中使用了另一个答案中建议的方法。
  • @slallum:start = item.start if item.start is not None else 0 条件表达式可能工作得很好(然后使用range(start, item.stop ..)。
【解决方案4】:

问题:

切片由startstopstep 参数组成,可以使用slice notationslice 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 分配合理的默认值是最简单的。它明确、简单、清晰、简洁。

完善实施

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

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

【讨论】:

  • /!\ or 如果 item.step 为 0。
  • “[或] 是明确的”。没那么多,少数值被隐含地解释为 False,如果您真的只想测试 None,这可能会出现问题。在该示例中,切片步长等于 0 可能会引发错误,但不会。更糟糕的是,你不能使用 0 作为 item.stop 的值,它会被长度 self 覆盖。
【解决方案5】:

有一种更简单的方法可以做到这一点(至少在 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]

【讨论】:

  • 这太棒了。我要注意的一件事是list(range(10)[0:20]) 不会抛出错误,这可能是有问题的!
  • slice(0, None, 1)TypeError: 'NoneType' object cannot be interpreted as an integer 的情况下失败。您应该检查 None 并处理这种情况。 glglgl 建议使用itertools.count()
  • @MathieuCAROFF 伙计,最初提出问题的人说他想要一个范围,而不是其他可迭代的。他从未表示希望能够在没有端点的情况下处理切片。如果这就是你想要的,那就去问你自己的问题,而不是在评论中问。
  • 对不起,我认为这是最漂亮的解决方案,这是唯一的缺点。
  • 此外,所有其他答案都大错特错(截至 2019-01-06)。
【解决方案6】:

这样的事情呢?

>>> 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]

【讨论】:

    【解决方案7】:

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

    您只是不能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]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-05-16
      • 2019-09-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-07
      • 1970-01-01
      相关资源
      最近更新 更多