<jupyter_start><jupyter_text>Delegating Iterators Often we write classes that use some existing iterable for the data contained in our class. By default, that class is not iterable, and we would need to implement an iterator for our class and implement the `__iter__` method in our class to return new instances of that iterator. But, if our underlying data structure for our class is already an iterable, there's a much quicker way of doing it - delegation. We'll start with a really simple example first:<jupyter_code>from collections import namedtuple

Person = namedtuple('Person', 'first last')
class PersonNames:
    def __init__(self, persons):
        try:
            self._persons = [person.first.capitalize()
                             + ' ' + person.last.capitalize()
                            for person in persons]
        except (TypeError, AttributeError):
            self._persons = []
persons = [Person('michaeL', 'paLin'), Person('eric', 'idLe'), 
           Person('john', 'cLeese')]
person_names = PersonNames(persons)<jupyter_output><empty_output><jupyter_text>Technically we can see the underlying data by accessing the (pseudo) private variable `_persons`.<jupyter_code>person_names._persons<jupyter_output><empty_output><jupyter_text>But we really would prefer making our `PersonNames` instances iterable.To do so we need to implement the `__iter__` method that returns an iterator that can be used for iterating over the `_persons` list.But lists are iterables, so they can provide an iterator, and that's precisely what we'll do - we'll **delegate** our own iterator, to the list's iterator:<jupyter_code>class PersonNames:
    def __init__(self, persons):
        try:
            self._persons = [person.first.capitalize()
                             + ' ' + person.last.capitalize()
                            for person in persons]
        except TypeError:
            self._persons = []
    
    def __iter__(self):
        return iter(self._persons)<jupyter_output><empty_output><jupyter_text>And now, `PersonNames` is iterable!<jupyter_code>persons = [Person('michaeL', 'paLin'), Person('eric', 'idLe'), 
           Person('john', 'cLeese')]
person_names = PersonNames(persons)
for p in person_names:
    print(p)<jupyter_output><NAME>
<NAME>
<NAME><jupyter_text>And of course we can sort, use list comprehensions, and so on - our PersonNames **is** an iterable. Here we sort the names based on the full name, then split the names (on the space) and return a tuple of first name, last name:<jupyter_code>[tuple(person_name.split()) for person_name in sorted(person_names)]<jupyter_output><empty_output><jupyter_text>Or, if we want to sort based on the last name:<jupyter_code>sorted(person_names, key=lambda x: x.split()[1])<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.0944206008583691, 'fraction_numerical': 0.000357653791130186, 'mean_word_length': 3.8559027777777777, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '35350', 'license': 'Apache-2.0', 'n_tokens_mistral': 820, 'n_tokens_neox': 795, 'n_words': 280, 'path': 'dd_1/Part 2/Section 04 - Iterables and Iterators/11 - Delegating Iterators.ipynb', 'repo_name': 'rebekka-halal/bg', 'size': '2811'}