Как добавить __len__ к объекту без __len__ в его определении типа данных? - PullRequest
0 голосов
/ 19 января 2019

Согласно документации, это не работает из-за этого:

Для пользовательских классов неявные вызовы специальных методов гарантированно будут работать правильно, только если они определены в типе объекта, а не всловарь экземпляра объекта.Такое поведение является причиной, по которой следующий код вызывает исключение:

>>> 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()

1 Ответ

0 голосов
/ 19 января 2019

Вы не можете добавить его к существующему объекту, поэтому создайте свой собственный класс-оболочку с определением уровня класса, которым вы управляете:

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)

Никакой другой код не нужно менять.

...