如何扩展,模仿或模拟范围功能?

时间:2015-05-21 00:55:41

标签: python

我为角色范围制作了一个小的生成器函数:

>>> def crange(start, end):
...     for i in range(ord(start), ord(end)+1):
...             yield chr(i)
...

然后我可以这样做:

>>> print(*crange('a','e'))
a b c d e

耶!但这不起作用:

>>> crange('a','e')[::2]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'generator' object is not subscriptable

这是有效的,但是O(n),不像range&#39; O(1):

>>> 'y' in crange('a','z')
True

这意味着从最大值110,000中搜索字符编号109,999大约需要0.35秒。 109999 in range(110000)当然是快速的。

那时,我的第一个想法是简单地将范围子类化。不幸的是:

>>> class A(range):
...     pass
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: type 'range' is not an acceptable base type

所以我想我必须以某种方式模仿它,允许我将字符作为参数传递,在内部像range一样工作,并产生字符。不幸的是,我不确定如何继续。我尝试了dir()

>>> print(*dir(range), sep='\n')
__class__
__contains__
__delattr__
__dir__
__doc__
__eq__
__format__
__ge__
__getattribute__
__getitem__
__gt__
__hash__
__init__
__iter__
__le__
__len__
__lt__
__ne__
__new__
__reduce__
__reduce_ex__
__repr__
__reversed__
__setattr__
__sizeof__
__str__
__subclasshook__
count
index
start
step
stop

让我可以看到其中有哪些功能,但我不确定他们正在做什么,或range如何使用它们。我查找了range的来源,但它在C中,我不知道在哪里找到它的Python包装器(它确实有一个,对吧?)。

我从哪里出发,我是否应该去那里?

2 个答案:

答案 0 :(得分:17)

  

那时,我的第一个想法是简单地将范围子类化。

range是Python2中的一个函数,是#34; final&#34; Python3中的类(more info here) - 在这两种情况下都不是你可以子类的东西。您需要创建一个从crange扩展为基类的类object

class crange(object):
  

这是有效的,但是O(n),与范围的O(1)

不同

在Python 3中,您将为对象定义__contains__方法。

  

对于未定义__contains__()的对象,成员资格测试首先通过__iter__()尝试迭代,然后通过__getitem__()尝试旧的序列迭代协议,请参阅语言参考中的此部分。

这允许Python确定值是否在您的范围内而不实际枚举范围。

举一个简单的例子,如果您的范围是1到1,000,000,那么确定23546是否在该范围内(1 < 23546 < 1000000)是微不足道的。当然,实际的实现有点复杂,并且增加了处理步长增量等的能力。

关于:

  

耶!但这不起作用:>>> crange('a','e')[::2]

在这种情况下,您需要在对象上定义__getitem__。以下是一些所需方法的示例:

class crange(object):
    def __init__(self, start, end, step=1):
        # initialize your range object
        self.start = start
        self.end = end
        self.step = step

    def __iter__(self):
        # enable iteration over your object
        # (assume step size is 1)
        for i in range(ord(self.start), ord(self.end)+1):
            yield chr(i)

    def __getitem__(self, i):
        # enable accessing items in your range by index
        # also enable crange('a','e')[::2]
        # (assuming step size of 1)
        if isinstance( i, slice ):
            # implement slicing 
        else:
            return chr(ord(self.start) + i)

    def __contains__(self, char):
        # enable O(1) determination of whether a value is in your range
        # (assume step size is 1)
        return ord(self.start) <= ord(char) < ord(self.end)

    def __len__(self):
        # return length (assuming step size of 1)
        return ord(self.end) - ord(self.start)

答案 1 :(得分:9)

添加到Martin Konecny的回答中。您可能希望对所有内容使用内部范围,并在chr和ord之间进行转换。

class crange:
    def __init__(self, *args, **kwargs):
        args = [ord(arg) for arg in args]
        kwargs = {key: ord(val) for key, val in kwargs.items()}
        self.range = range(*args, **kwargs)

    def __iter__(self):
        for n in self.range:
            yield chr(n)

    def __contains__(self, c):
        return ord(c) in self.range

    def __getitem__(self, i):
        if isinstance(i, slice):
            ret = crange('\x00')
            ret.range = self.range[i]
            return ret
        else:
            return chr(self.range[i])

    def __repr__(self):
        return  "crange({}, {})".format(
            repr(chr(self.range.start)), repr(chr(self.range.stop)))

r = crange('a', 'f')
print(list(r))
print('b' in r)
print('f' in r)
print(r[:2])

换句话说:如果我们不能将其子类化,我们可以使用object composition