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.
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.
- We will define each block as a class and determine its inputs and outputs.
- The outputs will be based on the inputs.
- 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
Post a Comment