【问题标题】:How to add __len__ to an object without __len__ on its data type definition?如何将 __len__ 添加到其数据类型定义中没有 __len__ 的对象?
【发布时间】:2019-01-18 23:28:22
【问题描述】:

根据文档,这不起作用,因为:

对于自定义类,特殊方法的隐式调用只有在对象类型上定义时才能保证正常工作,而不是在对象的实例字典中。这种行为是以下代码引发异常的原因:

>>> class C:
...     pass
...
>>> c = C()
>>> c.__len__ = lambda: 5
>>> len(c)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'C' has no len()

https://docs.python.org/3/reference/datamodel.html#special-method-lookup

我在没有__len__的函数生成器上试过这个,但我事先知道它的长度,然后,我尝试用c.__len__ = lambda: 5之类的东西给它打补丁,但它一直说生成器对象没有长度。

这是生成器:

def get_sections(loaded_config_file):
    for module_file, config_parser in loaded_config_file.items():
        for section in config_parser.sections():
            yield section, module_file, config_parser

我将生成器(没有长度)传递给另一个函数(然而,另一个生成器),它需要通过调用 len() 来获得可迭代长度:

def sequence_timer(sequence, info_frequency=0):
    i = 0
    start = time.time()
    if_counter = start
    length = len(sequence)
    for elem in sequence:
        now = time.time()
        if now - if_counter < info_frequency:
            yield elem, None
        else:
            pi = ProgressInfo(now - start, float(i)/length)
            if_counter += info_frequency
            yield elem, pi
        i += 1

https://github.com/arp2600/Etc/blob/60c5af803faecb2d14b5dd3041254ef00a5a79a9/etc.py

然后,当尝试将__len__ 属性添加到get_sections 时,出现错误:

get_sections.__len__ = lambda: calculated_length
for stuff, progress in sequence_timer( get_sections ):
    section, module_file, config_parser = stuff

TypeError: object of type 'function' has no len()

【问题讨论】:

  • 你有什么问题?您引用了文档中说它不会起作用的部分。如果要自定义len(),需要定义一个类。
  • 是的,这里最简单的事情就是编写一个带有__len__ 的自定义类包装器,它将您需要的所有其他内容委托给生成器对象

标签: python python-3.x generator


【解决方案1】:

您不能将它添加到现有对象中,因此请创建您自己的包装类,该类具有您可以控制的类级别定义:

class KnownLengthIterator:
    def __init__(self, it, length):
        self.it = it
        self.length = int(length)

    def __len__(self):
        return self.length

    def __iter__(self):
        yield from self.it

现在您只需将无效尝试更改为设置长度:

get_sections.__len__ = lambda: calculated_length

进行有效的重新包装,使get_sections 继续成为有效的生成器(yield from 会将所有迭代行为委托给包装的生成器),同时也暴露一个长度:

get_sections = KnownLengthIterator(get_sections, calculated_length)

无需更改其他代码。

【讨论】:

  • 我会让KnownLengthIterator 成为一个真正的迭代器。所以只需__iter__(self): return iter(self.it)__next__(self): return next(self.it)
  • @juanpa.arrivillaga:你可以这样做,但这不是这样做的方式;你刚刚违反了the iterator protocol。如果你定义__next____iter__必须是身份函数(除了return self什么都不做)。 Python 代码实际上依赖于能够通过测试 iter(x) is x 来检测迭代器,并让它仅适用于迭代器。 yield from 的优势在于,委托意味着即使像 send 这样的协程行为也可以继续工作(代价是使该类在技术上是可迭代的,而不是迭代器)。
  • 啊,这是一个好点。但是你可以在__iter__ 中使用return self。我认为维护协程行为可能是值得的,具体取决于用例。
【解决方案2】:

您可以通过以下方式进行操作

class A:
    def __init__(self):
        self.x = 1

a = A()
def b_fn(self):
    return self.x

def __len__(self):
    return 5

a.b = b_fn.__get__(a)
# A can be also obtained as type(a). 
A.__len__ = __len__.__get__(A)
a.b()
len(a)

5

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-19
    • 1970-01-01
    • 2016-10-03
    • 2014-07-03
    • 2020-06-11
    相关资源
    最近更新 更多