Binding Class Attributes In Python

You can also read this article from my LinkedIn here.

If you’re not interested in my personal motivation to do this, you can skip right to the Essential Python Background section.

Introduction

During my last semester, my friends and I were studying a course called Computer Architecture. This course covered topics concerning how a computer processor works and how it is built and wired up internally.
As a project, we were asked to build a software simulator that should be given input in the same manner a computer processor is given input, and then it produces the same output as the processor. After a lot of discussion, and despite facing some opposition, we decided that Python was the right tool for the job. The thing is, we had something in mind, that would make the whole process a trivial piece of cake but that something was not offered by default in Python. We had to build it ourselves. I was given the task of making this magic happen and I will explain what this magic was and how it ties up to the title of this article.

Personal Motivation

Simplified MIPS Datapath
The Above Picture demonstrates the data path we had to implement. What we had in mind was something like this.
  1. We will define each block as a class and determine its inputs and outputs.
  2. The outputs will be based on the inputs.
  3. We will make instances of each block as needed and after all blocks are instantiated, we will find a *magical* way to tell python that the input of this block is connected to the output of that block. So when the output of a block changes, the corresponding outputs of all other blocks will change automatically, without requiring any action on our behalf.

Essential Python Background

Before we can explain how this magic worked out, we must first learn some essential Python Background. This section will only give a brief explanation because the purpose of this blog is not to teach you what you can learn everywhere else. Instead, to introduce you the power of what you can do with these little things when you put them together. If you would like a detailed explanation, feel free to search the internet. Google is your friend after all ^_^.

Everything is just another class

In Python, everything is a class; an integer is an ‘int’ class, a float is a ‘float’ class, even a function is a ‘function’ class that has a method called __call__, Python automatically calls this method when you call the function. You call the function by putting a () after its name (and inserting arguments if necessary).
Knowing that you can pass around classes as arguments and you can store them as names, you can do the same to functions. Everything considered, a function is still a class, no?

You can define functions inside other functions

The process of defining a function is the process of creating a function class. Since you are free to create classes inside functions as you please, then you are also free to define functions inside of other functions. Python also has a way to make passing functions to other functions easier for you to read and use. You can google: Python Decorator Syntax to learn more.

Dealing with missing class attributes

When you attempt to access a class attribute that does not exist, Python does not immediately raise an exception. Instead, Python sees if the class you are accessing has a __getattr__() method. If it does have it, then Python calls this method, passes it the attribute name as a string, and gives you whatever it returns as the value of that attribute.

The getattr() builtin function

If you wish to access a certain attribute inside a class and you already know its name, then piece of cake, right? If the attribute is called value and the class instance is called x, then it’s as simple as x.value. But, what happens if the name of the attribute you are trying to access will be provided as a string during runtime? We can’t just type x.”value” , can we? Off-course we can’t. You may be tempted to spend half your lifetime writing, if name == “foo”: then x.foo else if name == “bar” then x.bar but you can see why this is a tedious task.
Luckily, Python has this builtin function called getattr() that takes 2 arguments, A class instance and an attribute name (as a string). It then returns the attribute having the name you provided from that instance, and offcouse if the attribute was not found, it will try to call the __getattr__() method from that instance and will raise an Exception if the __getattr__() was not found.

Mutable and Immutable Objects

Many Python objects are mutable, which means that they can be changed after they are created. These mutable objects include Lists and Dictionaries among others off-course. If a mutable object was passed somewhere else, and changed there. The change will also take effect in the original location, even if the object was not returned. This is because all these different names point to the same object. If you don’t believe me try this.
A = [1, 2, 3]
B = A
B.append(4)
print(A)
What do you think print(A) will output?

Not Magic But Python

While there might be many ways to accomplish what is required, here is the procedure I preferred.
First, we will define a class called MipsBlock. Inside the initialisation of this class, we will create a dictionary called getvalues. This dictionary will initially contain strings defined inside a tuple called slots as keys, and None as values. Later on, when we define blocks, we MUST define this slots tuple and make the block inherit from MipsBlock.


As an example, We will build a few Python classes to play around with. The next code snippet is pretty much self-explanatory. The @property decorator is used to allow us to access the output as an attribute.


We then aim to create a Wire object that will bind adder.input_no to mynumber.number, our wire object should take a first class, first attribute name, a second class and a second attribute name. Watch this and bear with me.


The wire class first defines a function called resolver without calling it. This resolver function, when called, will return the value of mynumber.number.
The wire class then gets the predefined “getvalues” dictionary and sets the resolver function (without calling it) as the value of the key “input_no”. Because dictionaries are mutable, this update will also take effect in the original getvalues dictionary.
At this point, What happens when we try to print(adder.input_no)? Since input_no is not defined as an attribute in either Addthree or its parent class MipsBlock, Python will try to call the __getattr__() of Addthree. If not found, Python will try to call the __getattr__() of MipsBlock. If not found, Python will raise an Exception. Let’s define the __getattr__() method for the MipsBlock class.


Python will now call the __getattr__() method of the MipsBlock class and will give it “input_no” as an attrname argument. If the input_no was defined in slots (and it was) the __getattr__ method will call a method called __resolve, pass attrname as an argument and return the value returned by this function.
And now the last step, we will define the __resolve method.


The __resolve method will take the attribute name as argument. Recall that we had a dictionary called getvalues that was created and had all attribute names defined in slots as keys and None as values, unless this None value was replaced by a resolver function during Wire() instantiation. We will get the corresponding value for this key from the dictionary and call it resolver. If the resolver was None, this means that the object has not been wired. Remember that calling this resolver returned the value of mynumber.number, right? We will call this resolver and return its value to __getattr__ which in turn returns it as the value of the adder.input_no and VIOLA!!
Let’s now try these tests:


Conclusion

The last example explained the concept with a very specific use-case. You, on the other hand might need to use this for something else. No matter what your application is, the concept remains the same. Feel free to use this concept in any project you have but Please provide a link to this article in your references if my article helped you out.


Comments

Featured Posts

Binding Class Attributes In Python (Part 2)

Hello World!!

Realtime Distributed Deduplication: How storagebox Works

Using Squid, Apache and Python To Develop a Captive Portal Solution

Building A Completely Serverless PyPI Repository On AWS

Bypassing VOIP ISP block in Asterisk