Understanding Python’s Iteration and Membership: A Information to __contains__ and __iter__ Magic Strategies – KDnuggets

 


Picture by Creator

 

For those who’re new to Python, you will have come throughout the phrases “iteration” and “membership” and puzzled what they imply. These ideas are basic to understanding how Python handles collections of information, corresponding to lists, tuples, and dictionaries. Python employs particular dunder strategies to allow these functionalities.

However what precisely are dunder strategies? Dunder/Magic strategies are particular strategies in Python that begin and finish with a double underscore, therefore the identify “dunder.” They’re used to implement numerous protocols and can be utilized to carry out a variety of duties, corresponding to checking membership, iterating over parts, and extra. On this article, we shall be specializing in two of a very powerful dunder strategies: __contains__ and __iter__. So, let’s get began.

 

Understanding Pythonic Loops with Iter Methodology

 

Think about a primary implementation of a file listing utilizing Python courses as follows:

class File:
	def __init__(self, file_path: str) -> None:
    	    self.file_path = file_path
   	 
class Listing:
	def __init__(self, recordsdata: Listing[File]) -> None:
    	    self._files = recordsdata

 

A simple code the place the listing has an occasion parameter that incorporates an inventory of File objects. Now, if we wish to iterate over the listing object, we must always be capable to use a for loop as follows:

listing = Listing(
	recordsdata=[File(f"file_{i}") for i in range(10)]
)
for _file in listing:
	print(_file)

 

We initialize a listing object with ten randomly named recordsdata and use a for loop to iterate over every merchandise. Easy sufficient, However whoops! You get an error message: TypeError: ‘Listing’ object is just not iterable.

What went flawed? ​​Properly, our Listing class is not set as much as be looped via. In Python, for a category object to turn into iterable, it should implement the __iter__ dunder methodology. All iterables in Python like Listing, Dictionaries, and Set implement this performance so we are able to use them in a loop.

So, to make our Listing object iterable, we have to create an iterator. Consider an iterator as a helper that provides us objects one after the other after we ask for them. For instance, after we loop over an inventory, the iterator object will present us with the subsequent component on every iteration till we attain the tip of the loop. That’s merely how an iterator is outlined and applied in Python.

In Python, an iterator should know find out how to present the subsequent merchandise in a sequence. It does this utilizing a technique known as __next__. When there are not any extra objects to present, it raises a particular sign known as StopIteration to say, “Hey, we’re done here.” Within the case of an infinite iteration, we don’t increase the StopIteration exception.

Allow us to create an iterator class for our listing. It should take within the checklist of recordsdata as an argument and implement the subsequent methodology to present us the subsequent file within the sequence. It retains observe of the present place utilizing an index. The implementation appears to be like as follows:

class FileIterator:
    def __init__(self, recordsdata: Listing[File]) -> None:
        self.recordsdata = recordsdata
        self._index = 0
    
    def __next__(self):
        if self._index >= len(self.recordsdata):
        	increase StopIteration
        worth = self.recordsdata[self._index]
        self._index += 1
        return worth

 

We initialize an index worth at 0 and settle for the recordsdata as an initialization argument. The __next__ methodology checks if the index overflows. Whether it is, it raises a StopIteration exception to sign the tip of the iteration. In any other case, it returns the file on the present index and strikes to the subsequent one by incrementing the index. This course of continues till all recordsdata have been iterated over.

Nevertheless, we aren’t performed but! Now we have nonetheless not applied the iter methodology. The iter methodology should return an iterator object. Now that we’ve got applied the FileIterator class, we are able to lastly transfer in the direction of the iter methodology.

class Listing:
    def __init__(self, recordsdata: Listing[File]) -> None:
        self._files = recordsdata
    
    def __iter__(self):
        return FileIterator(self._files)

 

The iter methodology merely initializes a FileIterator object with its checklist of recordsdata and returns the iterator object. That is all it takes! With this implementation, we are able to now loop over our Listing construction utilizing Python’s loops. Let’s examine it in motion:


listing = Listing(
	recordsdata=[File(f"file_{i}") for i in range(10)]
)
for _file in listing:
	print(_file, finish=", ")

# Output: file_0, file_1, file_2, file_3, file_4, file_5, file_6, file_7, file_8, file_9,

 

The for loop internally calls the __iter__ methodology to show this end result. Though this works, you may nonetheless be confused concerning the underlying workings of the iterator in Python. To know it higher, let’s use some time loop to implement the identical mechanism manually.

listing = Listing(
	recordsdata=[File(f"file_{i}") for i in range(10)]
)

iterator = iter(listing)
whereas True:
    strive:
        # Get the subsequent merchandise if accessible. Will increase StopIteration error if no merchandise is left.
        merchandise = subsequent(iterator)   
        print(merchandise, finish=', ')
    besides StopIteration as e:
        break   # Catch error and exit the whereas loop

# Output: file_0, file_1, file_2, file_3, file_4, file_5, file_6, file_7, file_8, file_9,

 

We invoke the iter operate on the listing object to amass the FileIterator. Then, we manually make the most of the subsequent operator to invoke the subsequent dunder methodology on the FileIterator object. We deal with the StopIteration exception to gracefully terminate the whereas loop as soon as all objects have been exhausted. As anticipated, we obtained the identical output as earlier than!

 

Testing for Membership with Incorporates Methodology

 

It’s a pretty widespread use case to test for the existence of an merchandise in a group of objects. For instance in our above instance, we might want to test if a file exists in a listing very often. So Python makes it easier syntactically utilizing the “in” operator.

print(0 in [1,2,3,4,5]) # False
print(1 in [1,2,3,4,5]) # True

 

These are majorly used with conditional expressions and evaluations. However what occurs if we do this with our listing instance?

print("file_1" in listing)  # False
print("file_12" in listing) # False

 

Each give us False, which is inaccurate! Why? To test for membership, we wish to implement the __contains__ dunder methodology. When it isn’t applied, Python fall backs to utilizing the __iter__ methodology and evaluates every merchandise with the == operator. In our case, it is going to iterate over every merchandise and test if the “file_1” string matches any File object within the checklist. Since we’re evaluating a string to customized File objects, not one of the objects match, leading to a False analysis

To repair this, we have to implement the __contains__ dunder methodology in our Listing class.

class Listing:
    def __init__(self, recordsdata: Listing[File]) -> None:
        self._files = recordsdata
    
    def __iter__(self):
        return FileIterator(self._files)
    
    def __contains__(self, merchandise):
        for _file in self._files:
        	# Verify if file_path matches the merchandise being checked
        	if merchandise == _file.file_path:
            	return True
    	return False

 

Right here, we modify the performance to iterate over every object and match the file_path from the File object with the string being handed to the operate. Now if we run the identical code to test for existence, we get the proper output!

listing = Listing(
	recordsdata=[File(f"file_{i}") for i in range(10)]
)

print("file_1" in listing)	# True
print("file_12" in listing) # False

 

 

Wrapping Up

 

And that’s it! Utilizing our easy listing construction instance, we constructed a easy iterator and membership checker to grasp the interior workings of the Pythonic loops. We see such design choices and implementations pretty usually in production-level code and utilizing this real-world instance, we went over the integral ideas behind the __iter__ and __contains__ strategies. Preserve practising with these strategies to strengthen your understanding and turn into a more adept Python programmer!
 
 

Kanwal Mehreen Kanwal is a machine studying engineer and a technical author with a profound ardour for knowledge science and the intersection of AI with medication. She co-authored the e book “Maximizing Productivity with ChatGPT”. As a Google Technology Scholar 2022 for APAC, she champions range and educational excellence. She’s additionally acknowledged as a Teradata Range in Tech Scholar, Mitacs Globalink Analysis Scholar, and Harvard WeCode Scholar. Kanwal is an ardent advocate for change, having based FEMCodes to empower ladies in STEM fields.

Recent articles