【问题标题】:How do I extend, mimic, or emulate the range function?如何扩展、模仿或模拟 range 函数?
【发布时间】:2015-08-02 11:24:43
【问题描述】:

我为字符范围做了一个小生成器函数:

>>> 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 的 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 包装器(它确实有一个,对吗?)。

我从这里去哪里,我应该去那里吗?

【问题讨论】:

  • “但它是用 C 语言编写的,我不知道在哪里可以找到它的 Python 包装器(它确实有一个,对吗?)” - 不。与 listdict 一样,range 的任何部分都不是用 Python 编写的。
  • 关于班级,也许是class A(object, range):
  • 虽然这似乎是一个有趣的问题,但我投票决定关闭它,因为它过于宽泛。除了通过range 的整个API 并复制它之外,没有什么好的方法可以做到这一点,所以答案必须描述range 所做的一切,所有用于自定义len 的钩子和切片等等大概需要 10 页。我建议搜索方法名称并查看 Python data model
  • 现有版本的替代品:def crange(start, end): return map(chr, range(ord(start), ord(end)))
  • 我不同意它过于宽泛。 OP已经有了一个核心方法,他需要做的是实现一个惰性列表,这本质上就是range()的作用。一旦他这样做了,他就可以着手实现范围 API 的其余部分,并提出有关它们的具体问题。 OP 可能想先看看 PyPi 上的 lazyarray 模块。我强烈建议 OP 尽快将他的问题缩小到惰性列表实现,以避免被关闭。

标签: python


【解决方案1】:

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

range 是 Python2 中的一个函数,也是 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 &lt; 23546 &lt; 1000000) 很简单。当然,实际的实现要复杂一些,并且增加了处理步进增量等的能力。

关于:

耶!但这不起作用:&gt;&gt;&gt; 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)

【讨论】:

  • 如果我没记错的话,__contains__()range() 中实现为:if step == 1: return start &lt;= num &lt; end / else: return start &lt;= num &lt; end and not (num - start) % step(不知道为什么我知道)。 crange() 的类似函数是:if step == 1: return ord(start) &lt;= ord(c) &lt; ord(end) / else: return ord(start) &lt;= ord(c) &lt; ord(end) and not (ord(c) - ord(start)) % step
  • @DougR.thx 填了一些方法,用了你的__contains__()方法
  • 很高兴能提供帮助。抱歉,我花了三遍才把整件事弄好。这是我喜欢在 SO 上看到的那种问题。
  • 您的 __getitem__() 函数似乎忽略了该步骤。即,如果我们做了my_crange = crange('a', 'm', 3)my_crange[1] == 'd'。您的 __getitem__() 将返回“b”。我觉得应该是return chr(ord(self.start) + i * step)
  • ...我不敢相信我犯了这样一个菜鸟错误。回顾我对__contains__() 的返回值,无需执行if/else...n % 1 == 0 表示任何整数。
【解决方案2】:

补充 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

【讨论】:

  • 我正要问在内部使用range 对象是否是个好主意... :)
  • self.range.__contains__(ord(c))ord(c) in self.range; self.range.__getitem__(i)self.range[i], ('crange('+repr(chr(self.range.start))+', '+ repr(chr(self.range.stop))+')')"crange({}, {})".format(self.range.start, self.range.stop).
  • 建议的更改,谢谢。让自己坚持使用对称的方法名称。 repr 是一团糟,没有借口:(。
猜你喜欢
  • 2023-04-02
  • 2011-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-15
  • 2019-12-24
  • 2019-05-27
  • 1970-01-01
相关资源
最近更新 更多