<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Noble-47</title>
    <description>The latest articles on Forem by Noble-47 (@noble47).</description>
    <link>https://forem.com/noble47</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F500783%2F076bec73-412f-4d67-bedb-959f032de9d8.jpg</url>
      <title>Forem: Noble-47</title>
      <link>https://forem.com/noble47</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/noble47"/>
    <language>en</language>
    <item>
      <title>Leveraging The Power Of Iteration Using Python Data Model</title>
      <dc:creator>Noble-47</dc:creator>
      <pubDate>Sun, 23 Feb 2025 22:32:59 +0000</pubDate>
      <link>https://forem.com/noble47/leveraging-the-power-of-iteration-using-python-data-model-e3k</link>
      <guid>https://forem.com/noble47/leveraging-the-power-of-iteration-using-python-data-model-e3k</guid>
      <description>&lt;p&gt;Iteration is a core part of many built-in objects in Python such as list and tuple objects. Just as with many other python features, the Python data model gives us a way to create iterable objects that work well with Python's idiomatic features giving our objects the powers needed to accomplish iterative tasks.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;This article is a follow up of my &lt;a href="https://dev.to/noble47/writing-pythonic-code-with-python-data-model-2j3o"&gt;previous Python Data Model article&lt;/a&gt; - Special Methods. In this article, I assume that you are already familiar with a few daunder/special methods including those for string representation and comparisons. If you are unfamiliar with any of these checkout &lt;a href="https://dev.to/noble47/writing-pythonic-code-with-python-data-model-2j3o"&gt;my previous post&lt;/a&gt; that covers what you need to continue with this article.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To make iterable objects, we need a good understanding of how Python implements iteration and what is required to make our own iterable objects. To explain iteration, we will be creating a coordinate geometry point object which is a bit mathematical as was mentioned in the &lt;a href="https://dev.to/noble47/writing-pythonic-code-with-python-data-model-2j3o"&gt;previous post&lt;/a&gt;. Along with making our objects iterable, we will also give our objects other cool functionalities that are common to it. We will begin by creating a coordinate geometry point class.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Point Object&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;For our Point object, we really just want to create a simple object that works well with the x and y coordinate system (the Cartesian plane) having some functions that are common to a coordinate point. We will begin our class definition by defining the daunder init method and paramters needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Point:
    def __init__(self, coordinates): 
        """
         coordinates : a list or tuple of two numbers,the first
                       specifies the x-axis value,the second
                       specifies the y-axis value
        """
        self.coordinates = tuple(coordinates)

    def __repr__(self):
        return f"Point([{','.join(str(v) for v in self.coordinates)}])"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our simple Point class requires only a single argument to be initialized, the coordinates argument which is expected to be a sequence that can be turned into a tuple object, this simply means that the coordinates can be a list or any iterable that returns two numbers. The use of coordinates as an iterable that returns numbers is mainly because it allows us to easily extend our class. If in the future we decide to simulate a n-coordinate system that has more than two axes, a Point class definition that specifies x and y as arguments for a 2-dimensional coordinate system point object would be quite difficult to extend especially if n is a bigger number relative to 2. However, in this article, most of our methods would comfortably work on a 2-dimensional coordinate system than any other. Why? simple is better than complex.&lt;/p&gt;

&lt;p&gt;A quick noticeable problem with the way the Point class is defined is that the x and y axis values of an instance would have to be accessed from &lt;code&gt;self.coordinates&lt;/code&gt; which is not ideal. The problem with letting users access the coordinates property of an instance is the fact that the user could knowingly or unknowingly assign it to another value which could break the code or cause unexpected behaviors. Let us consider a few cases&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; p = Point((1,2)) # we pass in a tuple as coordinate (1,2)
&amp;gt;&amp;gt;&amp;gt; x,y = p.coordinates # tuple unpacking to access x and y values
&amp;gt;&amp;gt;&amp;gt; print(f'{x}, {y}') 
# return : 1, 2
&amp;gt;&amp;gt;&amp;gt; p.coordinates = None # reassign coordinates
&amp;gt;&amp;gt;&amp;gt; x,y = p.coordinates
# return : Traceback (most recent call last): 
  File "&amp;lt;stdin&amp;gt;", line 1, in &amp;lt;module&amp;gt; 
TypeError: cannot unpack non-iterable NoneType object
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because in our implementation, we do not want users of our class instance to reassign the coordinates property of an instance of our class, we will have to implement a way that gives &lt;strong&gt;read-only access&lt;/strong&gt; to the values contained in the &lt;code&gt;coordinates&lt;/code&gt; property and we will also have to declare coordinates as a private variable. Let's begin with the last, making coordinates a private variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Point:
    def __init__(self, coordinates):
        self._coordinates = tuple(coordinates)

    def __repr__(self):
        return f"Point([{','.join(str(v) for v in self._coordinates}])"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not much of a change right? In our bid to make the coordinates property private, we only used a new variable with the same name except for the underscore in front. Python does not make a variable private, this is just an accepted way among Python programmers to tell others that 'this variable is not to be accessed directly and any error that results by you accessing this variable is completely on you'. For more about this, you can check out &lt;a href="https://www.geeksforgeeks.org/private-variables-python/" rel="noopener noreferrer"&gt;GeeksForGeeks article&lt;/a&gt; also check out the &lt;a href="https://docs.python.org/3/tutorial/classes.html#private-variables" rel="noopener noreferrer"&gt;official python docs&lt;/a&gt;. Now that we have made coordinates 'Private', we will now turn our attention to accessing the values in &lt;code&gt;_coordinates&lt;/code&gt; without exposing our 'private' variable. One way we can do this is to make our Point instance an iterable object that returns the next value in &lt;code&gt;_coordinates&lt;/code&gt; starting from index 0 every time it is looped over like in a for loop.&lt;/p&gt;

&lt;p&gt;The first step we will take to make our object iterable is to have a good understanding of what Python considers iterable. To do that, we will consult our favorite doctor, the Python docs (documentation), and here's what it says&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;iterable:&lt;br&gt;
An object capable of returning its members one at a time. Examples of iterables include all sequence types (such as list, str, and tuple) and some non-sequence types like dict, file objects, and objects of any classes you define with an &lt;strong&gt;iter&lt;/strong&gt;() method or with a &lt;strong&gt;getitem&lt;/strong&gt;() method that implements Sequence semantics. -- (&lt;a href="https://docs.python.org/3/glossary.html#term-iterable" rel="noopener noreferrer"&gt;python doc&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For our object to be considered an iterable object, it must implement the dunder iter method and an optional dunder getitem method. The &lt;code&gt;__iter__&lt;/code&gt; method is expected to return an iterator object as stated in the &lt;a href="https://docs.python.org/3/library/stdtypes.html#container.__iter__" rel="noopener noreferrer"&gt;Python documentation&lt;/a&gt;. Since it is required that our &lt;code&gt;__iter__&lt;/code&gt; method has to return an iterator object, we will also have to know what that is.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;iterator&lt;br&gt;
An object representing a stream of data. Repeated calls to the iterator's &lt;strong&gt;next&lt;/strong&gt;() method (or passing it to the built-in function next()) return successive items in the stream. When no more data are available a StopIteration exception is raised instead. At this point, the iterator object is exhausted and any further calls to its &lt;strong&gt;next&lt;/strong&gt;() method just raise StopIteration again. Iterators are required to have an &lt;strong&gt;iter&lt;/strong&gt;() method that returns the iterator object itself so every iterator is also iterable and may be used in most places where other iterables are accepted. One notable exception is code which attempts multiple iteration passes. A container object (such as a list) produces a fresh new iterator each time you pass it to the iter() function or use it in a for loop.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I know this is a bit of a lengthy text but let me break it down for you. An iterator object represents a stream of data and must have two methods : &lt;strong&gt;iter&lt;/strong&gt; and &lt;strong&gt;next&lt;/strong&gt;. The next method is responsible for returning successive (the next) items in the stream or raising a StopIteration if the stream is exhausted. while the iter method should return the same iterable object (self).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Notice the difference between the iterator object and the iterable object in Python. The iterable objects implement an optional `&lt;/em&gt;&lt;em&gt;getitem&lt;/em&gt;&lt;em&gt;` _method and the&lt;/em&gt; &lt;code&gt;__iter__&lt;/code&gt; &lt;em&gt;method that returns an iterator object. While the iterator object implements a&lt;/em&gt; &lt;code&gt;__next__&lt;/code&gt; &lt;em&gt;method to be consumed by the next built-in function and an&lt;/em&gt; &lt;code&gt;__iter__&lt;/code&gt; &lt;em&gt;method to return the iterator object. We can say that iterators are iterable but iterable objects are not iterators&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Point:
    def __init__(self, coordinates):
        self._coordinates = tuple(coordinates)

    def __repr__(self):
    return f"Point([{','.join(str(v) for v in self._coordinates}])"

    def __iter__(self):
        return PointIterator(self._coordinates)

class PointIterator:
    def __init__(self, coordinates):
        self.coordinates = coordinates
        self.count = 0

    def __next__(self):
        if self.count &amp;gt;= len(self.coordinates):
            raise StopIteration
        value = self.coordinates[self.count]
        self.count += 1
        return value

    def __iter__(self):
        return self
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our class &lt;code&gt;PointIterator&lt;/code&gt; satisfies the requirement to serve as an iterator and just by creating and returning an instance of &lt;code&gt;PointIterator&lt;/code&gt; in the class &lt;code&gt;Point&lt;/code&gt; &lt;code&gt;__iter__()&lt;/code&gt; method, we have successfully made our &lt;code&gt;Point&lt;/code&gt; objects iterable. Let's have a quick walkthrough of our &lt;code&gt;PointIterator&lt;/code&gt; class definition. As with most other class definitions, our iterator class has a &lt;code&gt;__init__&lt;/code&gt; method that takes in a single coordinate argument and then initializes the coordinates and count properties of our iterator class. The coordinates would be the coordinates of our Point object. For our use case, coordinates would be a tuple object. The count property is used as a reference to what value in &lt;code&gt;self.coordinates&lt;/code&gt; our &lt;code&gt;__next__&lt;/code&gt; method is to return. The &lt;code&gt;__next__&lt;/code&gt; method is where most of the heavy lifting is done. It first checks if the integer variable &lt;code&gt;self.count&lt;/code&gt; has exceeded or is equal to the length (&lt;code&gt;len&lt;/code&gt;) of the coordinates tuple. We use the &lt;code&gt;≥&lt;/code&gt; comparison because Python indexing starts from zero, so a tuple of two objects would have a length of 2 but a last index of 1. As stated in the Python docs, we raise a Stopiteration when self.count has reached or exceeded &lt;code&gt;len( self.coordinates)&lt;/code&gt;. If it hasn't, we just want to return the element at the index corresponding to the value of &lt;code&gt;self.count&lt;/code&gt; and then increment &lt;code&gt;count&lt;/code&gt; by 1. The &lt;code&gt;__iter__&lt;/code&gt; method simply returns &lt;code&gt;self&lt;/code&gt;, the same iterator object on which the built-in method &lt;code&gt;iter&lt;/code&gt; is called on. We can check what new capabilities our &lt;code&gt;Point&lt;/code&gt; instance now has&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# creating a 3 dimensional coordinate system point
&amp;gt;&amp;gt;&amp;gt; p = Point((1,2,3))

&amp;gt;&amp;gt;&amp;gt; print(p)
# return : Point([1,2,3])

# call the iter function on p
&amp;gt;&amp;gt;&amp;gt; it = iter(p)
&amp;gt;&amp;gt;&amp;gt; it 
# return : &amp;lt;__main__.PointIterator object at 0x7...&amp;gt; the repr of 'it'

# call next on it
&amp;gt;&amp;gt;&amp;gt; next(it)
# return : 1 the first number in coordinates

&amp;gt;&amp;gt;&amp;gt; next(it)
# return : 2

&amp;gt;&amp;gt;&amp;gt; next(it)
# return : 3

&amp;gt;&amp;gt;&amp;gt; next(it)
# return : Traceback (most recent call last): 
  File "&amp;lt;stdin&amp;gt;", line 1, in &amp;lt;module&amp;gt; 
  File "test.py", line 22, in __next__ 
    raise StopIteration 
StopIteration
# A StopIteration is raised as expected

"""Our object can also work well with for loops"""
&amp;gt;&amp;gt;&amp;gt; for coord in p:
...    print(coord)
# returns : 
1 
2 
3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have now successfully made our Point instance iterable but there are other ways to achieve this with way less lines of code. I'll show you two ways to quickly make our objects iterable without having to create our own iterator object.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Falling On The Tuple Iterator Object&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We already know that objects like tuples and list are iterables, this means that they also have an iterator objects that handles iteration behind the scene. Let's take a peek&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; it = iter(tuple([1,2,3]))
&amp;gt;&amp;gt;&amp;gt; it
# return : &amp;lt;tuple_iterator object at 0x...&amp;gt;
&amp;gt;&amp;gt;&amp;gt; next(it)
# return : 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this means for us is that we can make use of the tuple iterator for our objects since self.&lt;em&gt;coordinates is a tuple. All we have to do is to modify the __iter&lt;/em&gt;_ method in our Point class&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Point:
    def __init__(self, coordinates):
        self._coordinates = tuple(coordinates)

    def __repr__(self):
        return f"Point([{','.join(str(v) for v in self._coordinates}])"

    def __iter__(self):
        return iter(self._coordinates)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's test it…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; p = Point((1,2,3))
&amp;gt;&amp;gt;&amp;gt; for coords in p:
...    print(p)
...
# return:
1 
2 
3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which is exactly the same as before but with way less code.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Using Generator Expression&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This method is the most common, most simple, and also considered the most pythonic. Let's see how it is done&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Point:
    def __init__(self, coordinates):
        self._coordinates = tuple(coordinates)

    def __repr__(self):
        return f"Point([{','.join(str(v) for v in self._coordinates}])"
    def __iter__(self):
        points = (x for x in self._coordinates)
        return points
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the expression&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(x for x in self._coordinates)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;returns a generator object that can be consumed by the iter method for iteration. A simple example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; p = Point((1,2,3))
&amp;gt;&amp;gt;&amp;gt; iter(p)
# return : &amp;lt;generator object Point.__iter__.&amp;lt;locals&amp;gt;.&amp;lt;genexpr&amp;gt; at 0x...&amp;gt;
&amp;gt;&amp;gt;&amp;gt; for coord in p:
...    print(coord)
...
# return :
1 
2 
3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method is preferable because of the nature of generators, they save space by only computing the values when needed. It may not be so obvious for a 3-dimensional point but if we were dealing with a n-dimensional point where n could be a million, generator expression would be the right guy for the job. Just as a tip, because our object is now iterable, we can change our &lt;strong&gt;repr&lt;/strong&gt; code to look better…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    ... 
    def __repr__(self):
        return f"Point([{','.join(str(v) for v in self}])"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would work just fine because self which is an instance of our class is now iterable. One more thing to notice, we can shorten our &lt;strong&gt;iter&lt;/strong&gt;() method code by returning the generator without creating the point variable...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return (x for x in self._coordinates)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But I find the first to be more readable. Now that we have made our object iterable, there's still a little more work to do.&lt;/p&gt;

&lt;p&gt;First I'd like to address a little problem with our object. Making our object iterable did not make it subscriptable. In Python that means we cannot access the coordinates with its index&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; p = Point((2,3))
&amp;gt;&amp;gt;&amp;gt; p[0]
# return : Traceback (most recent call last): 
  File "&amp;lt;stdin&amp;gt;", line 1, in &amp;lt;module&amp;gt; 
TypeError: 'Point' object is not subscriptable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make our object subscriptable, we will have to give it a special method python calls in handling subscriptable objects - the &lt;strong&gt;getitem&lt;/strong&gt; method&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Point:
    def __init__(self, coordinates):
        self._coordinates = tuple(coordinates)

    def __repr__(self):
        return f"Point([{','.join(str(v) for v in self._coordinates}])"
    def __iter__(self):
        return iter(self._coordinates)

    def __getitem__(self, index):
        cls = type(self)
        if isinstance(index, int):
            return self._coordinates[index] 

        if isinstance(index,slice):
            return cls(self._cordinates[index])

        raise TypeError(f'{cls.__name__} indicies must be integers or slices, not {type(index).__name__})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our major focus is the &lt;strong&gt;getitem&lt;/strong&gt; method. It requires an index argument which should be an int or a slice object. If it is none of the mentioned object types, a TypeError should be raised. For checking the type of object &lt;code&gt;index&lt;/code&gt; is, we use the &lt;code&gt;isinstance&lt;/code&gt; function. Depending on if &lt;code&gt;index&lt;/code&gt; is a slice object or an int, in line with how Python list and tuple behaves, we return a new instance of Point that contains only the values that fall into the slice or return a single value. &lt;code&gt;type(self)&lt;/code&gt; is just a dynamic way of getting the class of &lt;code&gt;self&lt;/code&gt; without hard coding or passing it as an argument to &lt;strong&gt;getitem&lt;/strong&gt;. The error message is actually a simple modification of what a list would also raise. Let's test our object&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; p = Point((2,3,4)) # A 3-dimensional coordinate point
&amp;gt;&amp;gt;&amp;gt; p[0] # using an int as index
# return : 2
&amp;gt;&amp;gt;&amp;gt; p[-1] # negative indexing
# return : 4
&amp;gt;&amp;gt;&amp;gt; p[:2] # using a slice as index
# return : Point([2,3]) 
&amp;gt;&amp;gt;&amp;gt; p['a'] # using a str for index, expecting a TypeError
# return : Traceback (most recent call last): 
  File "&amp;lt;stdin&amp;gt;", line 1, in &amp;lt;module&amp;gt; 
  File "test.py", line 18, in __getitem__ 
    raise TypeError(f"{cls.__name__} indices must be integers or slices, not {type(index).__name__}")    
TypeError: Point indices must be integers or slices, not str
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, our object now has full support for slicing and indexing and also a nice formatted error message.&lt;/p&gt;

&lt;p&gt;Another feature I would like our Point object to have is read only attributes or labels that will have values corresponding to a coordinate in Point. For example, we can give a 2 dimensional coordinates the label 'xy' where calling &lt;code&gt;point.x&lt;/code&gt; returns the coordinate at index 0 and &lt;code&gt;point.y&lt;/code&gt; returns the coordinate at index 1. This can be done by adding &lt;code&gt;__getattr__&lt;/code&gt; and &lt;code&gt;__setattr__&lt;/code&gt; methods to our object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Point:
    def __init__(self, coordinates, label=None):
        self._coordinates = tuple(coordinates)
        self._label = label
    def __repr__(self):
        return f"Point([{','.join(str(v) for v in self._coordinates}])"
    def __iter__(self):
        return iter(self._coordinates)

    def __getitem__(self, index):
        cls = type(self)
        if isinstance(index, int):
            return self._coordinates[index] 

        if isinstance(index,slice):
            return cls(self._cordinates[index])

        raise TypeError(f'{cls.__name__} indicies must be integers or slices, not {type(index).__name__}')
    def __getattr__(self, name): 
        cls = type(self) 
        msg = f"{cls.__name__} object has not attribute {name}"
        if self._label is None: 
            raise AttributeError(msg)
        if len(name) == 1: 
            l = self._label.find(name) 
            if 0 &amp;lt;= l &amp;lt; len(self._coordinates): 
                return self._coordinates[l] 
        raise AttributeError(msg) 

    def __setattr__(self, name, value): 
        cls = type(self) 
        if len(name) == 1: 
            if self._label is not None and name in self._label: 
                err = f"readonly attribute {name}" 
            elif name.islower(): 
                err = f"cannot set attribute 'a' to 'z' in {cls.__name__}" 
            else: 
                err = "" 
            if err: 
                raise AttributeError(err) 
        super().__setattr__(name, value)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;__getattr__&lt;/code&gt; method serves as a fallback for Python whenever it cannot find an attribute of an object in the object or in any of the parent of the object. To implement our desired feature, our &lt;code&gt;__init__&lt;/code&gt; class is made to have an optional label argument. Our label parameter is set to be None by default meaning that this feature won't be accessible except the user passes in a label argument when creating the object. Before going into the technicalities, let's view the new behavior of our Point object&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; p = Point((1,2,3), label = 'xyz')
&amp;gt;&amp;gt;&amp;gt; p
# return : Point([1,2,3])
&amp;gt;&amp;gt;&amp;gt; p.x # get the x-axis value
# return : 1
&amp;gt;&amp;gt;&amp;gt; p.y # get the y-axis value
# return : 2
&amp;gt;&amp;gt;&amp;gt; p.z # get the z-axis value
# return : 3
&amp;gt;&amp;gt;&amp;gt; p.x = 10 # assign x attribute of p to a different number
# return : Traceback (most recent call last): 
  File "&amp;lt;stdin&amp;gt;", line 1, in &amp;lt;module&amp;gt; 
  File "test.py", line 45, in __setattr__ 
    raise AttributeError(err) 
AttributeError: readonly attribute x
&amp;gt;&amp;gt;&amp;gt; p.j = 2 # setting a new single letter lower case attribute
# return : Traceback (most recent call last): 
  File "&amp;lt;stdin&amp;gt;", line 1, in &amp;lt;module&amp;gt; 
  File "test.py", line 45, in __setattr__ 
    raise AttributeError(err) 
AttributeError: cannot set attribute 'a' to 'z' in Point
&amp;gt;&amp;gt;&amp;gt; p.some_attribute = 'some_value' # no error
&amp;gt;&amp;gt;&amp;gt; p.some_attribute
# return : 'some_value'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By defining the &lt;code&gt;__getattr__&lt;/code&gt; and the &lt;code&gt;__setattr__&lt;/code&gt; we have given our class instance a whole new ability. As stated before, the &lt;code&gt;__getattr__&lt;/code&gt; method enables our class instance to return specific values based on what label is used but this method doesn't stop a user from modifying (rather creating) a new attribute with one of our label's name. we can comment out the &lt;code&gt;__setattr__&lt;/code&gt; block of code and try the following code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; p = Point((1,2,3), label = 'xyz')
&amp;gt;&amp;gt;&amp;gt; p.x
# return : 1
&amp;gt;&amp;gt;&amp;gt; p.x = 10
&amp;gt;&amp;gt;&amp;gt; p.x
# return : 10
&amp;gt;&amp;gt;&amp;gt; p[0] # p still has 1 as it's first value
# return : 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember I did say that the &lt;code&gt;__getattr__&lt;/code&gt; is a fall back method whenever python can't find an attribute (like 'x' in this case). So whenever we call &lt;code&gt;p.x&lt;/code&gt; since &lt;code&gt;p&lt;/code&gt; by definition does not have an &lt;code&gt;x&lt;/code&gt; attribute, &lt;code&gt;__getattr__&lt;/code&gt; is called. But now that we have assigned an attribute &lt;code&gt;x&lt;/code&gt; to &lt;code&gt;p&lt;/code&gt; with a value 10, calling &lt;code&gt;p.x&lt;/code&gt; would return 10 but it won't affect the values in &lt;code&gt;self._coordinates&lt;/code&gt;. To stop this behavior, we defined the &lt;code&gt;__setattr__&lt;/code&gt; which Python calls when it wants to assign a new attribute to our object. &lt;/p&gt;

&lt;p&gt;By our definition, &lt;code&gt;__setattr__&lt;/code&gt; stops Python from assigning a new attribute whose name is in &lt;code&gt;label&lt;/code&gt; or is a lowercase alphabet (because they are special alphabets to our object). If the name of the attribute isn't among the label or a lowercase alphabet, then we return the default behavior by calling &lt;code&gt;super().__setattr__(name, value)&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;This is why some_attribute can be assigned to our object without raising an error.&lt;/p&gt;

&lt;p&gt;Our Point object now has some cool functionalities but lacks a lot, for starters, we cannot add or subtract two instances of our Point instance, find the magnitude of an instance, represent a two-dimensional point as a complex number, and many more. There may be a way to work around these insufficiencies but we can use the special method to implement them and I will show you how we can achieve that in the next article.&lt;/p&gt;

&lt;p&gt;It is important to state that our class definition makes a lot of assumptions without implementing a way to validate those assumptions. For instance, there's nothing stopping our user from passing in integers or a list to label instead of string. At the point of initialization, there would be no problem but when we try to get an attribute, our function tries to call find(x) on labels and if labels is not a string, an exception would be raised. We also didn't check if &lt;code&gt;len(label) == len(coordinates)&lt;/code&gt; though it doesn't have much effect on our code.&lt;/p&gt;

&lt;p&gt;For further study, I'd recommend the &lt;a href="https://docs.python.org/3/library/stdtypes.html#typeiter" rel="noopener noreferrer"&gt;Python's official docs&lt;/a&gt; as the major reference. You can also check out &lt;a href="https://www.oreilly.com/library/view/fluent-python/9781491946237/" rel="noopener noreferrer"&gt;Fluent Python by Luciano Ramalho&lt;/a&gt;. Another resource I came across was this &lt;a href="https://x.com/s_gruppetta/status/1534815067400589312" rel="noopener noreferrer"&gt;tweet&lt;/a&gt; and the &lt;a href="https://x.com/s_gruppetta/status/1535283758143877120" rel="noopener noreferrer"&gt;follow up tweet&lt;/a&gt; by &lt;a href="https://x.com/s_gruppetta" rel="noopener noreferrer"&gt;Stephen Gruppetta&lt;/a&gt; from which I learnt a great deal about iteration.&lt;/p&gt;

&lt;p&gt;I hoped you enjoyed this article and had fun make iterable objects.&lt;/p&gt;

</description>
      <category>python</category>
      <category>oop</category>
      <category>programming</category>
      <category>coding</category>
    </item>
    <item>
      <title>Writing Pythonic Code With Python Data Model</title>
      <dc:creator>Noble-47</dc:creator>
      <pubDate>Mon, 17 Feb 2025 00:14:52 +0000</pubDate>
      <link>https://forem.com/noble47/writing-pythonic-code-with-python-data-model-2j3o</link>
      <guid>https://forem.com/noble47/writing-pythonic-code-with-python-data-model-2j3o</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Special Methods&lt;/strong&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This apparent oddity is the tip of an iceberg that, when properly understood, is the key to everything we call Pythonic. The iceberg is called the Python data model, and it describes the API that you can use to make your own objects play well with the most idiomatic language features.&lt;/em&gt; - Luciano Ramalho (Fluent Python: clear, concise, and effective programming)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What's so special about the Python data model one may ask. Rather than giving a personal answer, why don't we do a little dive in and see what we can accomplish by understanding the data model. Data model simply speaks about how data is represented. In Python, data is represented by objects or to sound a bit technical, objects are Python's abstraction for data. The data model provides us with an API that allows our objects to play well with the 'under the hood' of Python programming.&lt;/p&gt;

&lt;p&gt;In our little dive into Python data model, we are going to specifically focus on the special methods. Special methods are class functions with special names that are invoked by special syntax. Defining these special methods in our class definitions can give our class instances some really cool Python powers like iteration, operator overloading, working well with context managers (the 'with' keyword), proper string representation and formatting, and many more. To show you how you could implement these special functions into your classes, we will consider two examples of situations where using these special functions would make our codes clearer and more Pythonic.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The first example is a little bit outside-the-box solution I came up with for creating a simple game of Rock-Paper-Scissors in Python and the second is going to be a bit mathematical in nature but I'm going to walk you through each line of code&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;A Simple Game Of Rock Paper Scissors&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Just in case you are not familiar with the Rock-Paper-Scissors game, it is originally a hand game usually played among two people that involves making signs of either Rock or paper or scissors. Knowing the whole history of the game doesn't really matter what is important is knowing how to determine the winner. In a conventional setting, a hand sign of rock would always win against scissors but will lose against paper, a hand sign of scissors would win against paper and lose to rock and obviously, paper would lose to scissors and win against rock. we can summarize this as shown below&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F52xdy54sjsum25wtqk2c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F52xdy54sjsum25wtqk2c.png" alt="Image of rock, paper, scissors game" width="460" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For our Python emulation of this game, we will limit the number of players to just two, one player would be the computer and the other would be the user. Also, this is not a machine learning article or a write-up about computer vision, our users would still have to type in an option between rock, paper, and scissors on the terminal for our program to work.&lt;br&gt;
Before we go into the actual coding, it's good that we take a step back and consider how we want our Python script to be. For my solution to this challenge, I will use the random module to enable the computer select a random option of either rock, paper, or scissors. To implement how our code evaluates the winner, I'm going to make the following assumptions:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;I'm also going to take an OOP approach; our rock, paper, and scissors will be treated as objects and not string variables. Rather than creating three separate classes for each, I'll create only one that can represent any of them. This approach would also allow me to show you how special methods make life easier. Now to the fun aspect!&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Class Definition&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Naming our class RPS may sound a bit odd, but I found the name 'RPS' to be a good fit cause each letter comes from the initials, R for Rock, P for Paper, and S for Scissors. What's important to note here is that creating an instance of our class requires two arguments: pick and name. We already stated that the users of our script would have to type in their selected option on the terminal, instead of making our users type in 'Paper' (which could be so stressful for them) why don't we just allow our user to type in 'P' (or 'p') to select 'Paper', that's what the pick stands for. The name property is the actual name e.g 'Paper'. So now that we know what each parameters is for, we can now inspect our class by creating an instance&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; p = RPS('P', 'Paper') # create an instance
&amp;gt;&amp;gt;&amp;gt; p.name
# return : Paper
&amp;gt;&amp;gt;&amp;gt; p.pick
# return : P
&amp;gt;&amp;gt;&amp;gt; print(p)
# return : &amp;lt;__main__.RPS object at 0x...&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Our class instance was created and has the right attributes but notice what we get when we try to print the contents of the variable holding our class instance. Before getting into the technical details of how our class instance returns the odd-looking string, let's update our class definition by adding a single special function and see the difference.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Now let's create an instance and try printing our class instance again&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; p = RPS('P', 'Paper')
&amp;gt;&amp;gt;&amp;gt; print(p)
# return : RPS(P, Paper)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;As we can see, by defining the '&lt;strong&gt;repr&lt;/strong&gt;' method we can achieve a better looking result. Let's make one more change to our class definition.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Now let's create an instance and test it again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; p = RPS('P', 'Paper')
&amp;gt;&amp;gt;&amp;gt; p
# return : RPS(P, Paper)
&amp;gt;&amp;gt;&amp;gt; print(p)
# return : Paper
&amp;gt;&amp;gt;&amp;gt; str(p)
# return : 'Paper'
&amp;gt;&amp;gt;&amp;gt; repr(p)
# return : 'RPS(P, Paper)'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To know what's going on here, we need to know a little about the print function. The print function converts all non-keyword arguments(like our p variable) to string using the built-in Python class &lt;code&gt;str&lt;/code&gt;. If calling &lt;code&gt;str()&lt;/code&gt; on our variable fails, python falls back on the built-in &lt;code&gt;repr&lt;/code&gt; function. When &lt;code&gt;str&lt;/code&gt; is called on our object, it looks for a &lt;code&gt;__str__&lt;/code&gt; method, if it finds none, it fails and then searches for a &lt;code&gt;__repr__&lt;/code&gt; method. Both the &lt;code&gt;__str__&lt;/code&gt; and the &lt;code&gt;__repr__&lt;/code&gt; methods are special methods used for string representation of our object. The &lt;code&gt;__repr__&lt;/code&gt; method gives the official string representation of our object while the &lt;code&gt;__str__&lt;/code&gt; method gives a friendly string representation of our object. I usually say that the &lt;code&gt;__repr__&lt;/code&gt; method is like talking to another developer and it usually shows how to call our class and the &lt;code&gt;__str__&lt;/code&gt; is like talking to a user of our program (like the player in this case), you would usually just want to return a simple string like "Paper" to show the user what he picked.&lt;/p&gt;

&lt;p&gt;Although I stated the &lt;code&gt;__repr__&lt;/code&gt; and &lt;code&gt;__str__&lt;/code&gt; as the two special functions in our class definition, there's actually a third special method, and yes it is the most common one, the &lt;code&gt;__init__&lt;/code&gt; function. It is used for initializing our class and called by the &lt;code&gt;__new__&lt;/code&gt; special method just before returning our class instance. Did I just mention another special method we haven't defined? yes, I did. It may also interest you to know that Python automatically adds some other special methods to our class. You can check them out by calling the built-in function &lt;code&gt;dir&lt;/code&gt; on our class instance like this&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; dir(p)
# returns : ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'pick']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Special functions or methods can be identified by the way they are named, they always begin with double underscores '&lt;strong&gt;' and end with double underscores '&lt;/strong&gt;' Because of this special way of naming these methods, they are commonly called daunder methods (Double + UNDERscores = DUNDER). So if a method name begins with double underscores, it is most likely but not certainly a special method. Why not certainly? this is simply because Python does not stop us from defining our own methods using the dunder syntax. Alright back to our game script.&lt;/p&gt;

&lt;p&gt;All that's left for us now is for us to let our script know how to determine a winner. As stated earlier, I will use comparison to evaluate a winner.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# comparison logic
Rock &amp;gt; Scissors
Scissors &amp;gt; Paper
Paper &amp;gt; Rock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;To implement this solution, I will add a dictionary and use the daunder greater_than method. The dictionary key would be the initials of Rock, Paper and Scissors. The value of each key would be the only other element that the key can defeat.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Notice the new lines of code, first the options dictionary and then the &lt;code&gt;__gt__&lt;/code&gt; method definition. With these new lines of code, let's see what new functionality our code now has.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# create a rock instance
&amp;gt;&amp;gt;&amp;gt; r = RPS('R', 'Rock')

# create a paper instance
&amp;gt;&amp;gt;&amp;gt; p = RPS('P', 'Paper')

# create a scissors instance
&amp;gt;&amp;gt;&amp;gt; s = RPS('S', 'Scissors')

&amp;gt;&amp;gt;&amp;gt; print(r,p,s)
# return : Rock Paper Scissors

&amp;gt;&amp;gt;&amp;gt; p &amp;gt; r # paper wins against rock
# return : True

&amp;gt;&amp;gt;&amp;gt; r &amp;gt; s # rock wins against scissors
# return : True

&amp;gt;&amp;gt;&amp;gt; s &amp;gt; p # scissors wins against paper
# return : True

&amp;gt;&amp;gt;&amp;gt; p &amp;lt; s # paper lose to scissors
# return : True

&amp;gt;&amp;gt;&amp;gt; p &amp;lt; r # paper lose to rock
# return : False

&amp;gt;&amp;gt;&amp;gt; p &amp;lt; s &amp;lt; r# paper lose to scissors which lose to rock
# return : True

&amp;gt;&amp;gt;&amp;gt; p &amp;gt;= r paper wins or tie to rock
# return : Traceback (most recent call last): 
  File "&amp;lt;stdin&amp;gt;", line 1, in &amp;lt;module&amp;gt; 
TypeError: '&amp;gt;=' not supported between instances of 'RPS' and 'RPS'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Just by adding to &lt;code&gt;__gt__&lt;/code&gt; special method, our class instances have gained magical powers (daunder methods are sometimes called magic methods and we can see why). By implementing the daunder gt method, our class instance now relates well with the &lt;code&gt;&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;&lt;/code&gt; symbols but not the &lt;code&gt;≥&lt;/code&gt; and &lt;code&gt;≤&lt;/code&gt; symbols. The reason is that &lt;code&gt;&amp;lt;&lt;/code&gt; is just the negation of &lt;code&gt;&amp;gt;&lt;/code&gt;. The special method for &lt;code&gt;&amp;lt;&lt;/code&gt; is &lt;code&gt;__lt__(self, x)&lt;/code&gt; which can just be the negation of calling &lt;code&gt;__gt__(self,x)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For the ≥ symbol, its special method is the &lt;code&gt;__ge__(self, x)&lt;/code&gt; and it must be defined for our object to work well with the &lt;code&gt;≥&lt;/code&gt; sign. But in this program, we can do without it. &lt;/p&gt;

&lt;p&gt;Another missing piece would be to check if two separate instances of Paper are equal.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; p1 = RPS("P", "Paper")
&amp;gt;&amp;gt;&amp;gt; p2 = RPS("P", "Paper")
&amp;gt;&amp;gt;&amp;gt; p3 = p1
&amp;gt;&amp;gt;&amp;gt; p1 == p2
# return : False

&amp;gt;&amp;gt;&amp;gt; p1 == p3
# return : True

&amp;gt;&amp;gt;&amp;gt; id(p1)
# return : 140197926465008

&amp;gt;&amp;gt;&amp;gt; id(p2)
# return : 140197925989440

&amp;gt;&amp;gt;&amp;gt; id(p3)
# return : 140197926465008

&amp;gt;&amp;gt;&amp;gt; id(p1) == id(p3)
# return : True

&amp;gt;&amp;gt;&amp;gt; id(p2) == id(p1)
# return False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The default operation of the equal comparison sign is to compare the id of the object. p1 and p2 are different class instances that happen to have the same attributes but their id differs and therefore are not equal. When we assign a variable to a class instance, we make that variable point to the address of the instance which is what we observe for p3 which has the same id as p1. We have the option of overriding how the equality comparison works on our object by defining and implementing our own &lt;code&gt;__eq__(self, other)&lt;/code&gt; method. But for this script, I will compare two instances using their pick attribute. Now that we have defined our class and know how it works, we are now ready to see the full implementation of the Python script&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;strong&gt;Putting It All Together&lt;/strong&gt;
&lt;/h4&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Let me walk you through the code. We are already familiar with the RPS class definition. If you recall, our code is meant to allow the computer to select choices at random and that's what the random module is for. The random module makes available the &lt;code&gt;choice&lt;/code&gt; function which allows the 'random' selection of an element from an iterable object e.g. a list in Python. The list in this case is the &lt;code&gt;option_list&lt;/code&gt;. Because our class is made to work with uppercase letters for comparison, it is necessary that we always initialize our objects with uppercase for the pick attribute. This is why we first convert the user's input to upper case (line 34) with the &lt;code&gt;.upper()&lt;/code&gt;. It is also possible that our user types in an unexpected character like 'Q' so we have to validate our user input by checking if the uppercase character is part of the valid options in &lt;code&gt;option_list&lt;/code&gt;. The mapping dictionary allows us to quickly convert the user's input to a corresponding instance of RPS after being validated. The evaluate_winner function makes use of the comparison symbol to determine the winner. Because we want the code to run in a loop until a winner is found, we make use of a while loop and when a winner is found, the evaluate_winner function returns True which will then break the loop and exit the game.&lt;/p&gt;

&lt;p&gt;Here is one of the various results of running the code&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fllgqmarh3ss70o8xrnmi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fllgqmarh3ss70o8xrnmi.png" alt="Image of running the python script" width="722" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our Python code runs as expected, although there could be a couple of improvements or new features to add. The most important thing is that we see how using special methods in our class definition gives our code a more Pythonic feel. Assuming we were to take a different approach such as using nested if statements, our evaluate_winner method would look something like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def evaluate_winner(user_choice, comp_choice):
    # check if user choice is 'R'
    if user_choice == 'R':
        # check if comp_choice is 'R'
        if comp_choice == 'R':
            # it is a tie
            ...
        elif comp_choice == 'S':
            # user wins
            ...
        else:
          # computer wins
          ...
    if ... 
     # do the same for when user_choice is 'S' and then for
     # when user_choice is 'P'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A problem with this approach other than the lengthy code is that if we desire to add a new element, diamond which can beat both rock and scissors but not paper (for an unknown reason), our if statements would begin to look really awkward. Whereas in our OOP approach, all we have to do is to modify the options dict like so&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;options = {"R" : ["S"], "P" : ["R"], "S" : ["P"], "D" : ["R", "S"]}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then we change the if statement in &lt;code&gt;__gt__&lt;/code&gt; to be&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def __gt__(self,x):    
    if x.pick in self.options[self.pick]:
        return True
    else:
        return False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we can make the statement shorter&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def __gt__(self, x):
   return True if x.pick in self.options[self.pick] else False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To conclude, here are some things you should note about using special methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You hardly (or never) call them directly yourself, let Python do the calling for you&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When defining functions that use the dunder naming syntax, you should consider that Python could one day define such a function and give it a different meaning. This could break your code or make it behave in unexpected ways&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You certainly don't have to implement every special method there is. Just a couple that you are really sure you need. Remember, simple is better than complex. If there's a simpler way you should use that instead&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To learn more about the Python data model I recommend the &lt;a href="https://docs.python.org/3/reference/datamodel.html" rel="noopener noreferrer"&gt;official Python documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'll also recommend reading &lt;a href="https://www.oreilly.com/library/view/fluent-python/9781491946237/" rel="noopener noreferrer"&gt;Fluent Python by Luciano Ramalho&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the first part of the topic, in the next part, we are going to be dealing with operator overloading and making iterable objects&lt;/p&gt;

&lt;p&gt;Hope you enjoyed this article!!!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>tutorial</category>
      <category>python</category>
      <category>oop</category>
    </item>
  </channel>
</rss>
