我应该如何处理 Python 中的包含范围?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29596045/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How should I handle inclusive ranges in Python?
提问by user200783
I am working in a domain in which ranges are conventionally described inclusively. I have human-readable descriptions such as from A to B
, which represent ranges that include both end points - e.g. from 2 to 4
means 2, 3, 4
.
我正在一个领域中工作,在该领域中,范围通常是包含性描述的。我有人类可读的描述,例如from A to B
,它们表示包括两个端点的范围 - 例如from 2 to 4
均值2, 3, 4
。
What is the best way to work with these ranges in Python code? The following code works to generate inclusive ranges of integers, but I also need to perform inclusive slice operations:
在 Python 代码中使用这些范围的最佳方法是什么?以下代码可用于生成包含范围的整数,但我还需要执行包含切片操作:
def inclusive_range(start, stop, step):
return range(start, (stop + 1) if step >= 0 else (stop - 1), step)
The only complete solution I see is to explicitly use + 1
(or - 1
) every time I use range
or slice notation (e.g. range(A, B + 1)
, l[A:B+1]
, range(B, A - 1, -1)
). Is this repetition really the best way to work with inclusive ranges?
我看到的唯一完整的解决方案是每次使用或切片符号(例如, , )时都明确使用+ 1
(或)。这种重复真的是处理包含范围的最佳方式吗?- 1
range
range(A, B + 1)
l[A:B+1]
range(B, A - 1, -1)
Edit:Thanks to L3viathan for answering. Writing an inclusive_slice
function to complement inclusive_range
is certainly an option, although I would probably write it as follows:
编辑:感谢 L3viathan 的回答。编写一个inclusive_slice
函数来补充inclusive_range
当然是一种选择,尽管我可能会这样写:
def inclusive_slice(start, stop, step):
...
return slice(start, (stop + 1) if step >= 0 else (stop - 1), step)
...
here represents code to handle negative indices, which are not straightforward when used with slices - note, for example, that L3viathan's function gives incorrect results if slice_to == -1
.
...
here 表示处理负索引的代码,当与切片一起使用时,这些代码并不简单 - 请注意,例如,如果slice_to == -1
.
However, it seems that an inclusive_slice
function would be awkward to use - is l[inclusive_slice(A, B)]
really any better than l[A:B+1]
?
但是,似乎inclusive_slice
函数使用起来很尴尬 -l[inclusive_slice(A, B)]
真的比l[A:B+1]
?
Is there any better way to handle inclusive ranges?
有没有更好的方法来处理包含范围?
Edit 2:Thank you for the new answers. I agree with Francis and Corley that changing the meaning of slice operations, either globally or for certain classes, would lead to significant confusion. I am therefore now leaning towards writing an inclusive_slice
function.
编辑 2:感谢您的新答案。我同意 Francis 和 Corley 的观点,即改变切片操作的含义,无论是全局的还是某些类的,都会导致严重的混淆。因此,我现在倾向于编写一个inclusive_slice
函数。
To answer my own question from the previous edit, I have come to the conclusion that using such a function (e.g. l[inclusive_slice(A, B)]
) would be better than manually adding/subtracting 1 (e.g. l[A:B+1]
), since it would allow edge cases (such as B == -1
and B == None
) to be handled in a single place. Can we reduce the awkwardness in using the function?
为了回答我之前编辑中的问题,我得出的结论是,使用这样的函数(例如l[inclusive_slice(A, B)]
)比手动加/减 1(例如l[A:B+1]
)要好,因为它允许边缘情况(例如B == -1
和B == None
)在一个地方处理。我们可以减少使用该功能的尴尬吗?
Edit 3:I have been thinking about how to improve the usage syntax, which currently looks like l[inclusive_slice(1, 5, 2)]
. In particular, it would be good if the creation of an inclusive slice resembled standard slice syntax. In order to allow this, instead of inclusive_slice(start, stop, step)
, there could be a function inclusive
that takes a slice as a parameter. The ideal usage syntax for inclusive
would be line 1
:
编辑 3:我一直在考虑如何改进使用语法,目前看起来像l[inclusive_slice(1, 5, 2)]
. 特别是,如果包含切片的创建类似于标准切片语法,那就太好了。为了允许这样做,而不是inclusive_slice(start, stop, step)
,可以有一个inclusive
将切片作为参数的函数。理想的使用语法inclusive
是 line 1
:
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
Unfortunately this is not permitted by Python, which only allows the use of :
syntax within []
. inclusive
would therefore have to be called using either syntax 2
or 3
(where s_
acts like the version provided by numpy).
不幸的是,Python 不允许这样做,它只允许:
在[]
. inclusive
因此,必须使用语法2
或3
(其s_
行为类似于numpy 提供的版本)调用。
Other possibilities are to make inclusive
into an object with __getitem__
, permitting syntax 4
, or to apply inclusive
only to the stop
parameter of the slice, as in syntax 5
. Unfortunately I do not believe the latter can be made to work since inclusive
requires knowledge of the step
value.
其他可能性是使用 制作inclusive
对象__getitem__
,允许语法4
,或inclusive
仅适用stop
于切片的参数,如语法5
。不幸的是,我不相信后者可以工作,因为inclusive
需要了解step
价值。
Of the workable syntaxes (the original l[inclusive_slice(1, 5, 2)]
, plus 2
, 3
and 4
), which would be the best to use? Or is there another, better option?
在可行的语法(正本l[inclusive_slice(1, 5, 2)]
,加2
,3
和4
),这将是最好用?或者还有其他更好的选择吗?
Final Edit:Thank you all for the replies and comments, this has been very interesting. I have always been a fan of Python's "one way to do it" philosophy, but this issue has been caused by a conflict between Python's "one way" and the "one way" proscribed by the problem domain. I have definitely gained some appreciation for TIMTOWTDIin language design.
最终编辑:感谢大家的回复和评论,这非常有趣。我一直是 Python 的“一种方式来做”哲学的粉丝,但是这个问题是由 Python 的“一种方式”和问题域所禁止的“一种方式”之间的冲突引起的。我确实对TIMTOWTDI在语言设计方面获得了一些赞赏。
For giving the first and highest-voted answer, I award the bounty to L3viathan.
由于给出了第一个和最高票数的答案,我将赏金奖励给了 L3viathan。
回答by L3viathan
Write an additional function for inclusive slice, and use that instead of slicing. While it would be possible to e.g. subclass list and implement a __getitem__
reacting to a slice object, I would advise against it, since your code will behave contrary to expectation for anyone but you — and probably to you, too, in a year.
为包含切片编写一个附加函数,并使用它代替切片。虽然可以例如子类列表并实现__getitem__
对切片对象的反应,但我建议不要这样做,因为您的代码将与除您之外的任何人的期望相反 - 并且可能在一年内对您也是如此。
inclusive_slice
could look like this:
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]
What I would do personally, is just use the "complete" solution you mentioned (range(A, B + 1)
, l[A:B+1]
) and comment well.
我个人会做的就是使用您提到的“完整”解决方案(range(A, B + 1)
,l[A:B+1]
)并发表评论。
回答by Francis Colas
I believe that the standard answer is to just use +1 or -1 everywhere it is needed.
我相信标准答案是在需要的地方只使用 +1 或 -1。
You don't want to globally change the way slices are understood (that will break plenty of code), but another solution would be to build a class hierarchy for the objects for which you wish the slices to be inclusive. For example, for a list
:
您不想全局更改切片的理解方式(这会破坏大量代码),但另一种解决方案是为您希望切片包含的对象构建类层次结构。例如,对于一个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]
Of course, you want to do the same with __setitem__
and __delitem__
.
当然,您想对__setitem__
and做同样的事情__delitem__
。
(I used a list
but that works for any Sequence
or MutableSequence
.)
(我使用了list
但适用于任何Sequence
或MutableSequence
。)
回答by plonser
If you don't want to specify the step size but rather the number of steps, there is the option to use numpy.linspace
which includes the starting and ending point
如果您不想指定步长而是指定步数,则可以使用numpy.linspace
包含起点和终点的选项
import numpy as np
np.linspace(0,5,4)
# array([ 0. , 1.66666667, 3.33333333, 5. ])
回答by ivan_pozdeev
Since in Python, the ending index is always exclusive, it's worth considering to always use the "Python-convention" values internally. This way, you will save yourself from mixing up the two in your code.
由于在 Python 中,结束索引始终是唯一的,因此值得考虑在内部始终使用“Python 约定”值。这样,您就可以避免在代码中混淆两者。
Only ever deal with the "external representation" through dedicated conversion subroutines:
仅通过专用转换子程序处理“外部表示”:
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)
Alternatively, you can mark the variables holding the "unusual" representation with the true Hungarian notation.
或者,您可以使用真正的匈牙利表示法标记包含“不寻常”表示的变量。
回答by Corley Brigman
Was going to comment, but it's easier to write code as an answer, so...
本来打算发表评论,但编写代码作为答案更容易,所以......
I would NOT write a class that redefines slicing, unless it's VERY clear. I have a class that represents ints with bit slicing. In my contexts, '4:2' is very clearly inclusive, and ints don't already have any use for slicing, so it's (barely) acceptable (imho, and some would disagree).
我不会写一个重新定义切片的类,除非它非常清楚。我有一个用位切片表示整数的类。在我的上下文中, '4:2' 显然是包容性的,并且整数还没有用于切片,所以它(几乎)是可以接受的(恕我直言,有些人会不同意)。
For lists, you have the case that you'll do something like
对于列表,您可能会执行以下操作
list1 = [1,2,3,4,5]
list2 = InclusiveList([1,2,3,4,5])
and later on in your code
稍后在您的代码中
if list1[4:2] == test_list or list2[4:2] == test_list:
and that is a very easy mistake to make, since list already HAS a well-defined usage.. they look identical, but act differently, and so this will be very confusing to debug, especially if you didn't write it.
这是一个很容易犯的错误,因为 list 已经有一个明确定义的用法..它们看起来相同,但行为不同,因此调试起来会非常混乱,尤其是如果您没有编写它。
That doesn't mean you're completely lost... slicing is convenient, but after all, it's just a function. And you can add that function to anything like this, so this might be an easier way to get to it:
这并不意味着你完全迷失了......切片很方便,但毕竟只是一个功能。您可以将该函数添加到类似的任何内容中,因此这可能是一种更简单的方法:
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]
However, this solution, like many others, (besides being incomplete... i know) has the achilles' heel in that it's just not as simple as the simple slice notation... it's a little more simple than passing the list as an argument, but still harder than just [4:2]. The only way to make that happen is to pass something differentto the slice, that could be interepreted differently, so that the user would know on reading it what they did, and it could still be as simple.
然而,这个解决方案,像许多其他解决方案一样,(除了不完整......我知道)有一个致命弱点,因为它不像简单的切片符号那么简单......它比将列表作为一个争论,但仍然比 [4:2] 更难。实现这一点的唯一方法是向切片传递一些不同的东西,这可以有不同的解释,这样用户在阅读它时就会知道他们做了什么,而且它仍然可以如此简单。
One possibility... floating point numbers. They're different, so you can see them, and they aren't too much more difficult than the 'simple' syntax. It's not built-in, so there's still some 'magic' involved, but as far as syntactic sugar, it's not bad....
一种可能性......浮点数。它们是不同的,因此您可以看到它们,并且它们并不比“简单”语法难太多。它不是内置的,所以仍然涉及一些“魔法”,但就语法糖而言,它还不错......
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]
The 3.0 should be enough to tell any python programmer 'hey, something unusual is going on there'... not necessarily whatis going on, but at least there's not surprise that it acts 'weird'.
3.0 应该足以告诉任何 python 程序员'嘿,那里发生了一些不寻常的事情'......不一定是发生了什么,但至少它的行为“奇怪”并不奇怪。
Note that there's nothing unique about that to lists... you could easy write a decorator that could do this for any class:
请注意,列表并没有什么独特之处……您可以轻松编写一个可以为任何类执行此操作的装饰器:
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)
or
或者
@inclusiveclass
class inclusivelist(list):
pass
The first form is probably more useful though.
不过,第一种形式可能更有用。
回答by Rcynic
Without writing your own class, the function seems to be the way to go. What i can think of at most is not storing actual lists, just returning generators for the range you care about. Since we're now talking about usage syntax - here is what you could do
无需编写自己的类,该函数似乎就是要走的路。我最多能想到的不是存储实际列表,只是返回您关心的范围的生成器。由于我们现在谈论的是使用语法 - 这是您可以做的
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
And then use as:
然后用作:
list(closed_range('1:5:2'))
[1,3,5]
Of course you'll need to also check for other forms of bad input if anyone else is going to use this function.
当然,如果其他人要使用此功能,您还需要检查其他形式的错误输入。
回答by B. M.
It's difficult and probably not wise to overload such basic concepts.
with a new inclusivelist class, len(l[a:b]) in b-a+1 which can lead to confusions.
To preserve the natural python sense, while giving readability in a BASIC style, just define :
重载这些基本概念是困难的,也可能是不明智的。b-a+1 中有一个新的 inclusivelist 类 len(l[a:b]) ,这可能会导致混淆。
为了保留自然的 Python 意义,同时以 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
then you can manage as you want, keeping the natural python logic :
然后你可以随意管理,保持自然的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
回答by shaunc
Focusing on your request for best syntax, what about targeting:
专注于您对最佳语法的要求,定位如何:
l[1:UpThrough(5):2]
You can achieve this using the __index__
method:
您可以使用以下__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
Now you don't even need a specialized list class (and don't need to modify global definition either):
现在您甚至不需要专门的列表类(也不需要修改全局定义):
>>> l = [1,2,3,4]
>>> l[1:UpThrough(2)]
[2,3]
If you use a lot you could use shorter names upIncl
, downIncl
or even
In
and InRev
.
如果您使用很多,您可以使用较短的名称upIncl
,downIncl
甚至
In
和InRev
。
You can also build out these classes so that, other than use in slice, they act like the actual index:
您还可以构建这些类,以便除了在切片中使用之外,它们的作用类似于实际索引:
def __int__(self):
return self.stop
回答by Abhijit
Instead of creating API that is not conventional or extending data types like list, it would be ideal to create a Slice
function a wrapper over the built-in slice
so that you can pass it across any where, a slicing is requiring.
Python has support for this approach for some exceptional cases, and the case you have could warrant for that exception case. As an example, an inclusive slice would look like
与其创建非传统的 API 或扩展数据类型(如列表),不如创建一个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)
And you can use it for any 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)
Finally you can also create an inclusive range called irange
to complement the inclusive slice (written in lines of OPs).
最后,您还可以创建一个包含范围,称为irange
补充包含的切片(以 OP 行编写)。
def irange(start, stop, step):
return range(start, (stop + 1) if step >= 0 else (stop - 1), step)
回答by Raghu
I am not sure if this is already covered, this is how I handled it to check if my variable is within a defined range:
我不确定这是否已经涵盖,这是我处理它以检查我的变量是否在定义的范围内的方式:
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")
This code will return "In Limit" since I have expanded my range by 1 hence making it inclusive
此代码将返回“In Limit”,因为我将范围扩大了 1,因此包含在内