我在一个领域工作,其中范围通常被包含在内。我有人类可读的描述,例如from A to B
,它代表包含两个端点的范围 - 例如from 2 to 4
表示2, 3, 4
。
在Python代码中使用这些范围的最佳方法是什么?以下代码用于生成包含的整数范围,但我还需要执行包含切片操作:
def inclusive_range(start, stop, step):
return range(start, (stop + 1) if step >= 0 else (stop - 1), step)
我看到的唯一完整解决方案是每次使用+ 1
或切片表示法时明确使用- 1
(或range
)(例如range(A, B + 1)
,{{1} },l[A:B+1]
)。这种重复真的是与包容范围一起工作的最佳方式吗?
修改:感谢L3viathan的回答。编写range(B, A - 1, -1)
函数来补充inclusive_slice
当然是一种选择,尽管我可能会写如下:
inclusive_range
def inclusive_slice(start, stop, step):
...
return slice(start, (stop + 1) if step >= 0 else (stop - 1), step)
这里代表处理负索引的代码,与切片一起使用时并不简单 - 例如,注意,如果...
,L3viathan函数会给出不正确的结果。
但是,slice_to == -1
函数似乎很难使用 - inclusive_slice
真的比l[inclusive_slice(A, B)]
好吗?
有没有更好的方法来处理包容范围?
编辑2:感谢您的新答案。我同意Francis和Corley的观点,即无论是在全球范围内还是在某些类别中,改变切片操作的含义都会导致严重的混淆。因此,我现在倾向于编写l[A:B+1]
函数。
为了回答上一次编辑中我自己的问题,我得出结论,使用这样的函数(例如inclusive_slice
)会比手动添加/减去1(例如l[inclusive_slice(A, B)]
)更好,因为它允许在一个地方处理边缘情况(例如l[A:B+1]
和B == -1
)。我们可以减少使用函数的尴尬吗?
编辑3:我一直在考虑如何改进使用语法,目前看起来像B == None
。特别是,如果包含切片的创建类似于标准切片语法,那将是好的。为了允许这个,而不是l[inclusive_slice(1, 5, 2)]
,可能有一个函数inclusive_slice(start, stop, step)
,它将一个切片作为参数。 inclusive
的理想用法语法为行inclusive
:
1
不幸的是,Python不允许这样做,只允许在l[inclusive(1:5:2)] # 1
l[inclusive(slice(1, 5, 2))] # 2
l[inclusive(s_[1:5:2])] # 3
l[inclusive[1:5:2]] # 4
l[1:inclusive(5):2] # 5
内使用:
语法。因此,必须使用语法[]
或inclusive
调用2
(其中3
的行为类似于the version provided by numpy)。
其他可能性是s_
成为inclusive
对象,允许语法__getitem__
,或仅将4
应用于inclusive
参数切片,如语法stop
。不幸的是,由于5
需要了解inclusive
值,我不相信后者可以工作。
可行的语法(原始step
,加l[inclusive_slice(1, 5, 2)]
,2
和3
),哪个最好用?或者还有另一种更好的选择吗?
最终编辑:感谢大家的回复和评论,这一直非常有趣。我一直是Python的粉丝"做到这一点的一种方式"哲学,但这个问题是由Python"单向"之间的冲突造成的。和单程"被问题域所禁止。我在语言设计方面肯定对TIMTOWTDI有所了解。
为了给出第一个也是最高票的答案,我将赏金给予L3viathan。
答案 0 :(得分:14)
为包含切片编写一个附加函数,并使用它而不是切片。虽然有可能例如子类列表并实现__getitem__
对切片对象做出反应,我建议不要这样做,因为你的代码的行为与除了你之外的任何人的期望都相反 - 也可能在一年内也与你有关。
inclusive_slice
可能如下所示:
def inclusive_slice(myList, slice_from=None, slice_to=None, step=1):
if slice_to is not None:
slice_to += 1 if step > 0 else -1
if slice_to == 0:
slice_to = None
return myList[slice_from:slice_to:step]
我个人所做的只是使用您提到的“完整”解决方案(range(A, B + 1)
,l[A:B+1]
)并发表评论。
答案 1 :(得分:9)
因为在Python中,结束索引总是独占的,所以值得考虑在内部始终使用“Python-convention”值。这样,您就可以避免在代码中混淆两者。
只能通过专用转换子程序处理“外部表示”:
def text2range(text):
m = re.match(r"from (\d+) to (\d+)",text)
start,end = int(m.groups(1)),int(m.groups(2))+1
def range2text(start,end):
print "from %d to %d"%(start,end-1)
或者,您可以使用true Hungarian notation标记包含“异常”表示的变量。
答案 2 :(得分:5)
如果您不想指定步长而是指定步数,可以选择使用包含起点和终点的numpy.linspace
import numpy as np
np.linspace(0,5,4)
# array([ 0. , 1.66666667, 3.33333333, 5. ])
答案 3 :(得分:4)
我相信标准答案是在需要的地方只使用+1或-1。
您不希望全局更改切片的理解方式(这将破坏大量代码),但另一种解决方案是为您希望切片包含的对象构建类层次结构。例如,对于list
:
class InclusiveList(list):
def __getitem__(self, index):
if isinstance(index, slice):
start, stop, step = index.start, index.stop, index.step
if index.stop is not None:
if index.step is None:
stop += 1
else:
if index.step >= 0:
stop += 1
else:
if stop == 0:
stop = None # going from [4:0:-1] to [4::-1] since [4:-1:-1] wouldn't work
else:
stop -= 1
return super().__getitem__(slice(start, stop, step))
else:
return super().__getitem__(index)
>>> a = InclusiveList([1, 2, 4, 8, 16, 32])
>>> a
[1, 2, 4, 8, 16, 32]
>>> a[4]
16
>>> a[2:4]
[4, 8, 16]
>>> a[3:0:-1]
[8, 4, 2, 1]
>>> a[3::-1]
[8, 4, 2, 1]
>>> a[5:1:-2]
[32, 8, 2]
当然,您希望对__setitem__
和__delitem__
执行相同操作。
(我使用list
,但适用于任何Sequence
或MutableSequence
。)
答案 4 :(得分:4)
没有编写自己的类,该函数似乎是要走的路。我最多可以想到的不是存储实际的列表,只是为你关心的范围返回生成器。因为我们现在正在谈论使用语法 - 这是你可以做的
def closed_range(slices):
slice_parts = slices.split(':')
[start, stop, step] = map(int, slice_parts)
num = start
if start <= stop and step > 0:
while num <= stop:
yield num
num += step
# if negative step
elif step < 0:
while num >= stop:
yield num
num += step
然后用作:
list(closed_range('1:5:2'))
[1,3,5]
当然,如果其他人要使用此功能,您还需要检查其他形式的错误输入。
答案 5 :(得分:3)
准备发表评论,但编写代码作为答案更容易,所以......
我不会写一个重新定义切片的类,除非它非常清楚。我有一个代表切片整数的类。在我的背景下,&#39; 4:2&#39;非常清楚包容,并且它没有任何用于切片的用途,所以它(几乎)不可接受(imho,有些人不同意)。
对于列表,您可能会遇到类似
的情况list1 = [1,2,3,4,5]
list2 = InclusiveList([1,2,3,4,5])
以及稍后的代码
if list1[4:2] == test_list or list2[4:2] == test_list:
这是一个非常容易犯的错误,因为列表已经有了明确定义的用法......它们看起来完全相同,但行为不同,所以调试会非常混乱,特别是如果你没有写下来。
这并不意味着你完全迷失了...切片很方便,但毕竟它只是一个功能。你可以将这个函数添加到这样的东西,所以这可能是一个更简单的方法来实现它:
class inc_list(list):
def islice(self, start, end=None, dir=None):
return self.__getitem__(slice(start, end+1, dir))
l2 = inc_list([1,2,3,4,5])
l2[1:3]
[0x3,
0x4]
l2.islice(1,3)
[0x3,
0x4,
0x5]
然而,这个解决方案,像许多其他人一样,(除了不完整......我知道)有阿喀琉斯&#39;因为它不像简单的切片符号那么简单......它比将列表作为参数传递更简单,但仍然比[4:2]更难。实现这一目标的唯一方法是将一些不同的传递给切片,这可能会以不同的方式进行解释,这样用户就会知道他们做了什么,而且仍然可以这么简单。
一种可能性......浮点数。它们是不同的,所以你可以看到它们,并且它们并不比简单的“它们”困难得多。句法。它不是内置的,所以还有一些神奇的“魔法”。涉及,但就语法糖而言,它还不错......
class inc_list(list):
def __getitem__(self, x):
if isinstance(x, slice):
start, end, step = x.start, x.stop, x.step
if step == None:
step = 1
if isinstance(end, float):
end = int(end)
end = end + step
x = slice(start, end, step)
return list.__getitem__(self, x)
l2 = inc_list([1,2,3,4,5])
l2[1:3]
[0x2,
0x3]
l2[1:3.0]
[0x2,
0x3,
0x4]
3.0应该足以告诉任何python程序员&嘿,那里发生了一些不寻常的事情......不一定正在发生什么,但至少在那里#&# 39;并不奇怪它的行为很奇怪。
请注意,列表中没有任何独特之处......您可以轻松编写可以为任何类执行此操作的装饰器:
def inc_getitem(self, x):
if isinstance(x, slice):
start, end, step = x.start, x.stop, x.step
if step == None:
step = 1
if isinstance(end, float):
end = int(end)
end = end + step
x = slice(start, end, step)
return list.__getitem__(self, x)
def inclusiveclass(inclass):
class newclass(inclass):
__getitem__ = inc_getitem
return newclass
ilist = inclusiveclass(list)
或
@inclusiveclass
class inclusivelist(list):
pass
第一种形式可能更有用。
答案 6 :(得分:3)
重载这些基本概念很困难也许并不明智。
使用新的包含列表类len(l [a:b])在b-a + 1中可能导致混淆。
为了保持自然的蟒蛇感,同时以BASIC风格提供可读性,只需定义:
STEP=FROM=lambda x:x
TO=lambda x:x+1 if x!=-1 else None
DOWNTO=lambda x:x-1 if x!=0 else None
然后您可以根据需要进行管理,保持自然的python逻辑:
>>>>l=list(range(FROM(0),TO(9)))
>>>>l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>l[FROM(9):DOWNTO(3):STEP(-2)] == l[9:2:-2]
True
答案 7 :(得分:3)
专注于您的最佳语法请求,如何定位:
l[1:UpThrough(5):2]
您可以使用__index__
方法实现此目的:
class UpThrough(object):
def __init__(self, stop):
self.stop = stop
def __index__(self):
return self.stop + 1
class DownThrough(object):
def __init__(self, stop):
self.stop = stop
def __index__(self):
return self.stop - 1
现在你甚至不需要专门的列表类(并且不需要修改 全球定义:)
>>> l = [1,2,3,4]
>>> l[1:UpThrough(2)]
[2,3]
如果您经常使用,可以使用较短的名称upIncl
,downIncl
或甚至
In
和InRev
。
您还可以构建这些类,以便除了在切片中使用它们之外 像实际指数一样:
def __int__(self):
return self.stop
答案 8 :(得分:3)
不是创建非传统的API或扩展像list这样的数据类型,最好是在内置Slice
上创建一个slice
函数作为包装器,这样你就可以传递它任何地方,需要切片。
对于某些特殊情况,Python支持这种方法,并且您可以保证该异常情况。例如,包含切片看起来像
def islice(start, stop = None, step = None):
if stop is not None: stop += 1
if stop == 0: stop = None
return slice(start, stop, step)
您可以将它用于任何sequence types
>>> range(1,10)[islice(1,5)]
[2, 3, 4, 5, 6]
>>> "Hello World"[islice(0,5,2)]
'Hlo'
>>> (3,1,4,1,5,9,2,6)[islice(1,-2)]
(1, 4, 1, 5, 9, 2)
最后,您还可以创建一个名为irange
的包容性范围,以补充包含切片(以OP行开头)。
def irange(start, stop, step):
return range(start, (stop + 1) if step >= 0 else (stop - 1), step)
答案 9 :(得分:0)
我不确定这是否已经涵盖,这是我处理它的方法,以检查我的变量是否在定义的范围内:
my var=10 # want to check if it is in range(0,10) as inclusive
limits = range(0,10)
limits.append(limits[-1]+1)
if(my_var in limits):
print("In Limit")
else:
print("Out of Limit")
此代码将返回“ In Limit”,因为我将范围扩大了1,从而使其具有包容性
答案 10 :(得分:0)
此解决方案适用于整数以及负数和浮点数,使用math
和numpy
:
def irange(start, stop=None, step=1):
if stop is None:
start, stop = 0, start
return list(start + numpy.arange(floor((stop - start) / step) + 1) * step)
答案 11 :(得分:-1)
也许inclusive软件包很有帮助。