According to the documentation, this does not work because of this:
For custom classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary. That behaviour is the reason why the following code raises an exception:
>>> 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
I had tried this on a function generator, which does not have __len__
, but I knew beforehand its length, then, I tried monkey patch it with something like c.__len__ = lambda: 5
, but it kept saying the generator object had no length.
This is the generator:
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
I was passing the generator (which has no length) to this other function (yet, another generator), which requires the iterable length by calling 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
Then, when trying to add the __len__
attribute to get_sections
, hence the error:
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()
You can't add it to an existing object, so make your own wrapper class that has a class level definition you control:
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
Now you just change your invalid attempt to set a length of:
get_sections.__len__ = lambda: calculated_length
to a valid rewrapping that makes get_sections
continue to be a valid generator (yield from
will delegate all iteration behaviors to the wrapped generator), while exposing a length too:
get_sections = KnownLengthIterator(get_sections, calculated_length)
No other code needs to change.