Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / artificial-intelligence / machine-learning

Python Generators and Classes

4.53/5 (7 votes)
16 Jun 2020CPOL4 min read 18.4K   147  
In this article we go a bit further with generators and classes.
This module talks about Python's generator functions for iterators, classes, inheritance, and magic methods.

Introduction

This is the third module in our series on learning Python and its employment in machine learning (ML) and artificial intelligence (AI). In the previous module, we took a look at data structures and loops. Now let's go a bit further with generators and classes.

Generators

One way to create your own iterator is by using a generator function. A generator function uses the yield keyword to pass the next iterator value to the caller. This is similar to the yield return keyword in C#. Once the function returns, there's nothing left to iterate over.

Let's demonstrate the yield keyword using a generator function that yields the first n numbers on the Fibonacci sequence:

Python
def fibonacci(n):
    a = 1
    b = 1
    for i in range(n):
        if i < 2:
            yield 1
        else:
            c = a + b
            a = b
            b = c
            yield c

You can now use this function like you use functions such as range, such as in a loop:

Python
for f in fibonacci(10):
    print(f)

This prints the first ten Fibonacci numbers.

You can also use generator functions to yield infinitely many elements.

Classes

Same as C# or Java, Python has classes. Python offers all the standard features of object-oriented programming.

Let's walk through an example of a simple class in Python:

Python
from math import sqrt

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def length(self):
        return sqrt(self.x ** 2 + self.y ** 2)

The __init__ method is the constructor.

length is a class method.

The first argument of a class method refers to the class instance that's being worked on. By convention, this is called self. (You can name it something else, but no one ever does that.) The role of self is much like the role of this in C# and Java, a reference to the current object. The difference in Python is that you cannot use just x rather than self.x, and that Python requires you to explicitly include it as the first method argument.

You can now use the class like this:

Python
v = Vector(1, 1)
print(v.length())
print(v.x)
print(v.y)

The x and y attributes can be accessed as you see above, but they can be modified as well:

Python
v.x = 2
print(v.length())

Python does not have access modifiers such as public and private. All variables are publicly accessible. Starting an attribute name with an underscore serves as a way to tell users of your class that they are not supposed to work with that attribute, but it's not enforced by the language.

Inheritance

Let's demonstrate how you can derive from a class in Python. We'll create a base class Document and a derived class Book:

Python
class Document:
    def __init__(self, author, content):
        self.author = author
        self.content = content

    def length(self):
        return len(self.content)

    def info_summary(self):
        return "Document written by " + self.author

class Book(Document):
    def __init__(self, author, content, pages):
        super().__init__(author, content)
        self.pages = pages

    def info_summary(self):
        return "Book written by {} of {} pages".format(self.author, self.pages)

The Book class derives from Document. In the Book class's __init__ method, this line calls the constructor of the superclass.

Python
super().__init__(author, content)

The info_summary function is overridden in Book (nothing like an override keyword is needed), and there is no mention of length in Book so it's just derived from Document.

Python
book = Book("me", "... content ...", 50)
print(book.length())
print(book.info_summary())

If you want to check whether a certain object is of a certain class, use the isinstance function:

Python
print(isinstance(book, Book)) # True
print(isinstance(book, Document)) # True
print(isinstance(book, object)) # True

doc = Document("someone else", "...")
print(isinstance(doc, Book)) # False
print(isinstance(doc, Document)) # True

Unlike C# and Java, Python supports multiple inheritance: instead of writing class Book(Document), you can write class Book(Document, AnotherClass, PerhapsEvenMore).

If the superclasses have methods with the same name, only one of them can be derived in the child class. When a method is called (that's not explicitly overridden), Python uses an algorithm named C3 linearization to determine the order in which to look in the superclasses. If you want to check out the so-called method resolution order, you can look at the YourClassName.__mro__ attribute. Here is an artificial example to demonstrate that:

Python
class A:
    pass

class B:
    pass

class C:
    pass

class D(A, C):
    pass

class F(B, C):
   pass

class G(A):
    pass

class H(F, B, D, A):
    pass

print(H.__mro__)

This outputs (<class '__main__.H'>, <class '__main__.F'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.A'>, <class '__main__.C'>, <class 'object'>) so you know that Python will first look in the H class, followed by B, D, A, and finally C.

Magic Methods

Python classes offer a lot of "magic methods," allowing you to do operator overloading, treat your class instance as an iterator, and much more.

A magic method is just like a normal method, but with a specific name in the format __method_name__. You already know one magic method, __init__. Another example is the __add__ magic method, to overload the + operator:

Python
class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

v1 = Vector(3, 2)
v2 = Vector(4, 1)
v3 = v1 + v2

The __iter__ and __next__ magic methods enable you to iterate over your instance. This method returns the next iterated value or raises StopIteration to indicate the end.

Python
class Fibonacci:
    def __init__(self, n):
        self.prev = 1
        self.prev_prev = 1
        self.n = n
        self.i = 0

    def __iter__(self):
        return self

    def __next__(self):
        self.i += 1
        if self.i == self.n + 1:
            raise StopIteration
        if self.i <= 2:
            return 1
        else:
            current = self.prev + self.prev_prev
            self.prev_prev = self.prev
            self.prev = current
            return current

for fib in Fibonacci(10):
    print(fib)

This is just the surface of magic methods, there is a lot more you can do. If you're interested, refer to this guide.

Conclusion

In this module, we've talked about generator functions for iterators, classes, inheritance, and magic methods. Now that we've walked through the Python basics, we can take a global look at machine learning-related packages in Python.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)