5.1 Members
5.1.1 naming
5.1.2 static/class
5.1.3 dunder
5.2 Descriptors
5.2.1 Introduciton
5.2.2 Example
5.3 Inheritance
5.3.1 super()
5.3.2 the abc
5.3.3 multiple

Object oriented programming in Python

5.1 Members

Members are the attributes and methods. Attributes are like "variables", and methods are "inside" functions.

5.1.1 Naming conventions: Private names and name mangling

Python encourage the use of one "_" (underscore) to indicate private names. Python enforce the use of two "__" to indicate name mangling.

Simply speaking, "name mangling" means when you use a leading d(ouble)under(score) without trailing underscores to bind a name inside a class definition, the actual name is transormed to _ClassName__varname.

Example 5.1

    

Use class attributes to store data.

Example 5.2

    

You can also use a singleton class and define class attributes to save memory.

5.1.2 Instance, class, and static methods

Instance methods are the methods bound to an instance of a class. The instance is the caller of this method. The first parameter of instance methods is reserved for self. Static methods do not operate on an instance or a class directly, and the caller can be either an instance or a class. Class methods act on a class. An instance can also be a caller. The first parameter is cls.

For example, we can define a Student class to store the grades. But we'll see that grading does not rely on a single student.

Example 5.10

    

Suppose now we need a class specifically for the grades of the first-year students, but we want to keep the structure of the general Student class. For practical usage, we want to read data from a spreadsheet and create instances for each student. For example:

Student spreadsheet

We could define a static method to read in the record.

Example 5.11

    

But when you want specific methods defined in the FirstYear class, you cannot call it from the instance created by read. This is where we need the @classmethod decorator.

5.1.3 Special methods: __call__() and __new__()

Introduction to the __call__ method

We start by introducing the idea of "callable". An object obj is callable when you can call it. In other words, when obj() or obj(*args, **kwargs) has meaning. Examples include, a function, a class, and an instance.

The reason that most of these objects are callable is the implementation of the __call__ method. Note that when you see dunders prior and after the methods' name, the methods are special methods.

Example 5.3

    

We can use the callable feature to achieve some goals. For example, when we want to count how many times an instance is called. We can use

Example 5.4

    

Do not confuse yourself with __init__ and __call__. A simple way to think about them is than __init__is used when the instance is on the left-hand side and __call__ is used when the instance is on the right-hand side.

Class as decorators

Every callable object can be a decorator. Why? Remember that a decorator @decor is just another of using func = decor(func). Let's use the Counter class to keep track of the times a function is called.

Example 5.5

    

5.2 Descriptors and the myths of @property

5.2.1 Introduction

You probably have used the @property decorator. For example, we can define a class that records the temperature of a day:

Example 5.6

    

We do not want people to arbitrarily modufy the value of temperature, so we can use the property decorator to add protection to the temperature attribute.

Example 5.7

    

We know that @property turns an attribute into a property. Namely, am attribute with methods bound to it. The myths are that property is an example of a wider concept known as descriptor. Descriptor have their own syntax when you define it. Descriptors make attributes of a class behave in ways you need it. The syntax is referred to as the descriptor protocol:

Descriptors require an instance to be passed as an attribute to a class:

Example 5.8

    

Let's turn Example 5.7 into a descriptor style.

Example 5.9

    

After this whole lot of effort, you wonder, why do we need descriptors at all? One use case is the so-called "lazy property". Each run of a machine learning algorithm can take a very long time. We do not want to recompute the same not-so-easy-to-get results again and again. So if sometimes we already have a set of results, we prevent the algorithm from running again.

source: https://realpython.com/python-descriptors/#how-to-use-python-descriptors-properly

    

5.2.2 An example

Now let's combine what we learned. Imagine we need to define a class which requires us to define a decorator inside:

Example 5.12

    

💡Can you think of other ways?

5.3 Inheritance

Inheritance is an important feature of object-oriented programming (OOP), which Python supports. I assume you have used inheritance from time to time (for example, class MyNN(torch.nn/Module)), so we will first review some basics of inheritance and talk about multiple inheritance and why you should not use unless you are sure what you are doing.

Consider a situation when you want to implement a sequence of machine learning algorithms, including neural networks (DNN, CNN, RNN, etc.), tree-based models (boosting trees and random forest), correlation based models (PCA, PLS etc.), and penalty-based models (Lasso, ridge, etc.).

You need to implement all these many algorithms and organize them properly. Design the structure of you class/functions before you start coding.

One way to approach this task is bottom-up: think about the commmon features and try to group the algorithms together. Then all is left is a bunch of detailed implementations.

An example structure can be as follows:

UML

We abstract the common features from all these classes. At least all them need data, therefore

We then move on to a lower level. Take NeuralNets as an example, all neural nets a forward step and a train method. Therefore,

The drawback of writing our code in this way is that if we unintentiously make an instance of NeuralNets, we run into trouble calling the forward and train_model methods. We will see ways to fix it.

5.3.1 The super() instance

The official document says that super() only returns a "proxy object".

Example 5.12

    

5.3.2 The Abstract Base Classes

The abstract base class in Python is a way to provide a base class that only specify the API but prevents us from create an instance. This way, we can better structure our code.


    

Later on, we can define DNN, CNN, and RNN classes to inherit the base NeuralNet class. Detailed implementations will happens there.

5.3.3 Multiple inheritance

Multiple inheritance allows us to derive classes from multiple parent classes. The idea is simple, if we want functions implemented in different parent classes, we can combine them by multiple inheritance. The only thing to keep in mind is the method resolution order (MRO). Python adopts linearization-combine methods to determine the MRO (🎨 Time to draw!). When using multiple inheritance, make sure the chain is through.


    

We quickly skimmed some of the aspects of the inheritance feature of Python, skipping many details. This tutorial provides you with more background information.

Misc

1. function and method

Example 5.13

    

2. a useful decorator

Example 5.14

    

Back⏎