Notes I’ve taken from the videos I watched, today. This is my attempt at Feynman-ing (below), what I learnt so far.

Classes and Object Oriented Programming started to come together for me, when I saw Kushal using them.

To use my father’s carpentry analogy, I could in theory just hammer nails into wood to join them.
But to make a really strong joint, I could use other methods.
I could screw pieces of wood together, which is markedly better than just nailing them.
I could chisel wood and create a dovetail or mortise joint.

So it all comes down to understanding what I want to do and figuring out the best technique to get it done.

Like I mentioned in the toot, I could in theory just kludge together all my primitives and create something.
But, if I want to create something beautiful and something that I (and others) can understand, I need to use a different method.
Object Oriented Programming, is a popular one.

Objects and Types (In Python)

We have objects and types/classes.
We can think of them like we do about cells in our body.
Python for the most part is natively made of objects.
Nearly every primitive I have come across in Python is an object.
Strings are objects and work a certain way / Skin cells work a certain way.
Dictionaries work a certain weay / Optical cells in the eye, work in an very specific manner.
Integers are objects and have their own ways of doing things, as do white blood corpuscles.

Objects are a special type of something.

A class makes objects.
An int class makes integers. A string class makes strings. And so on and so forth.

With Python even classes (the constructors) are objects.
So, there must be some kind of God / Creator thing that makes these primitives / originator classes.
And there is.
It’s the type class.
It’s the base class, the bottom turtle, if you will.
The type class creates all these primitive classes, which we then use to create other objects for our use.
The type of the int (integer) class is type as is the type of the str (string) class.
They are all objects of type type.

Which basically, again to use a physical analogy means that type is the iron forge, which i then use to create factories, which i then use to fashion usable things.
The forge lets me create lug nuts and chassis’ and then I take all these, to create a car factory, which lets me make cars of different types and sizes, with varying amounts of power.
The forge lets me create some mixer, which i can then choose to setup different sorts of mixer factories. Some can make ice cream, some can make porridge.
type (the forge), creates the int class (the factory), which lets me create all sorts of integer numbers to play around with.
type (the forge), creates the dictionary class (the factory), which lets me store and sling data about in various ways.

Note: This might not be true of other programming languages (for e.g. int might be a base primitive.)

Attributes

Every class can have attributes (data, or methods to manipulate data).
Those attribute themselves could be classes, having their own attributes.

Now each of those takes memory.
Python doesn’t gobble up all our memory however, because some (most?) objects share memory.

Run dir on an object to get it’s attributes.
For example, I could do this on the str class to see what it has.

>>> dir(str)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

I spy with my little eye, that any string object lets itself be split or joined or lets its case be altered and much, much more!

I can go into any object I want (with few exceptions) and change its attributes!
For example, I type os.sep to get the path separator. (on a linux machine it’s / and on windows it’d be \ and if python ever ran on classic Mac os I guess it’d be :
I can just change os.sep to be ! or hahaha or whatever i want!
Or I could just add whatever attribute I want to any object!
In short, I can set and get attributes on any object I want!
Sounds pretty free-ing! Will have to see what this means in practice!

Aha
The inbuilt functions that I have learnt are not the norm.
Most code is written in classes and functions are available as methods of those classes.
Ruby natively works this way.
Python pragmatically, has created some functions as primitives.
(One possible reason could be, for example, rather than have a len method on every in built class, they musta thought it easier to create a primitive len function that worked on different classes and data types)
In real life (and across programming languages) I should mostly be looking at object.method(), for e.g. some_list.pop()
If I want to look at how to manipulate something, the general heuristic is to look at methods first. Does an object support manipulating itself through itself? After all, it knows itself best! :)

If I want to know what methods an object supports, I can of course call dir on it, but a better cleaner way is to call help on it. (help(list)). It shows, more clearly, the methods an object supports.

Why Classes

Do I need to create classes?
Of course not, I have been creating programs without them all these months.
A couple of reasons I’d create a class,

  1. To stop kludginess. I don’t to expose a list of dictionaries, that contain more dictionaries that have lists as their values. better to abstract them away from the user (future me) and present simpler interfaces.
  2. I love to write well crafted prose. Classes represent complete sentences in human language. Nouns and verbs amounting to a sentence that makes sense and is well crafted.

How do i create a class

Aha
Just realised that when I create a class, I am creating a new type of primitive for myself.
My own data type!
I can create my own numbering system if i want to!
Muahahahahahahaha
(comes back to reality)

class somethingsomething(object):
	pass

That is all I need. Just to say class, create a class with this here name! And *Abracadabra-Alakazoom*, Python goes ahead and creates a new class for us!

As I wrote above, this is a factory, to create new somethingsomething objects.
I can do that by just assigning it a name. Like Iron man summoning his suit out of thin air.

somethingblue = somethingsomething()

And boom! The object `somethingblue` materialises!

This is just a blank template, of course.
I cannot do much with this. (Right now, I don’t even know, what I can imbue this with, or use it for :P)
So I guess, the rest of the course will probably deal with how to make classes work and function the way I want them to.

I can add attributes to my classes just by declaring them.

somethingblue.color='sky'

or

somethingblue.liquid='ocean'

or

somethingblue.code=0x0000FF

And then i could `dir(somethingblue)` and it will give me them attributes, along with a list of everything it has inherited from the parent class (here the `type` class)

A neater way of looking at only what I defined, is to do vars(somethingblue)

Aha
Heuristic - While i can add attributes willy-nilly to any object I want to, it’s bad form.
The idea being that the factory (the class) should ideally provide a set of default attributes and values, so that not too much monkeying is required.
This process/template of assigning sane attributes is called a constructor.

The way we create a class is two fold (very Smalltalk-esque)

  1. I create a class.
  2. I add its attributes.

So when Python goes to create an object of the class, it does it like quite similarly

  • it creates as new object when i say object = class_whatever(), using the __new__ method
  • it then goes looking to see if there is an __init__ method in the class, and if it exists, then to use that to setup the default attributes
  • it uses the self (by convention) argument, to assign those attributes to the object. (I am applying shampoo to mine own self)
  • and then assigns it to the variable that’s supposed to catch the instance.
    (I always somehow assumed it was the other way around. A variable in memory was assigned and then created like a building. This is more nature like. Stuff grows and then is assigned a name)
    I can absolutely go do what I want, assign attributes however, but if I want to stay sane, I’d rather follow convention

And that’s about all I studied today.
Follow along the my Python OOP train here.


P.S. The Feynman Method.