And we are done with objects!
This course finally gave me what I was looking for all these months.
The ability to think of and reason about Python, so that I can then think of and reason about, how to build my own programs.
Notes
- Abstract base classes
- Sounds all high and mighty, but does something pretty logical and simple
- Helps me test if my object is of a generic (of sorts) type. I don’t want to test if 5 is an integer or a float or any of the other various county types in Python. I just want to see if it’s a number, of some sort. The abstract base class
numbers
helps me do that. - There are such classes for strings and files, I hear.
- Will go into learning more about them in detail later, as I actually write more Python. But at least I know what they are now.
** Part 3, Context Managers**
Again, Context Managers, sound like something really complex, but they are just a matter of writing and doing things in a certain manner.
It’s something like a Tiny Habit stack loop, I learnt in the Atomic Habits book, as I was building up new habits.
- When I do this,
- then I will do this immediately after.
- and when I am done, I will do that.
Context Managers have a similar paradigm.
- With this object (as this variable, optionally)
- I will run the
__enter__
method - then after, I will do stuff
- and when I am done, I will run the
__exit__
method
- I will run the
- I can add the context manager protocol to my objects too, by impletementing the
__enter__
and__exit__
methods in my classes - They are used in a particular context, where you want to turn something on and then turn something off, before and after that context, thus Context Manager.
- I can set something up in my
__enter__
method and then tear it down in my__exit__
method. - This could be, for example, a network connection, a file, a redefinition of some sort, etc.
- The
__enter__
method must return self if all has to work well. - The
__exit__
block, takes four arguments, one forself
, one for an error class, one for the object, and one for the address in memory, the traceback. I can use them to catch errors in the main body of my with block, and then decide what the appropriate way to deal with them is. - A good example is using redirecting the
print
function’s output to a file, using a context manager. (The print function can write to a file, when I specify it as an output device like soprint ("Hello there", file=f)
- While I might be able to catch errors in the body of the context manager, I cannot if something happens during
__enter__
. just something to be aware of.
- I can set something up in my
- With this object (as this variable, optionally)
Contextlib is a library of context managers, Python already provides us.
import contextlib
contextlib.redirect_stdout/err
will redirect stuff from the default output (the screen) to whereever else you want it to.
** Part 4, Properties and Descriptors **
- A property is an attribute, that acts like getters/setters in other languages. It uses methods to set other attributes in the class (within the bounds we set). For e.g. a
.temp
property that will let you set temperatures, but only in the range you specify. not below 18C and not above 25C. - set by putting
@property
atop a getter function (let’s saytemp
) and then defining the setter properties in an identically namedtemp
function again but decorated with@function_name.setter
; in our case@temp.setter
- the whole actual way to do it is like a contortionist twisting into a pretzel shape. Getting fluent with this will take time and practice.
- it basically (to my mind) is like me giving someone something, and that someone goes and magician like, does something marvellous with that something, instead of just storing it in their pocket. it looks like assigning data to an attribute, but behind the scenes there are methods, just waiting to weave spells on said data.
- When I take a property out of a class and abstact it, so that it can be used by many classes, it’s called a descriptor. (kinda like abstracting functions out of program files and into modules)
- To kinda formally state, a descriptor is a class whose instances are supposed to be class attributes in other classes.
- To fit the descriptor protocol, a descriptor class needs to implement…
- a
__get__(self, host_instance, host_class)
method with a corresponding__set__(self, host_instance, new_value_to_set)
method
- a
- These are class specific. As in, the descriptor is shared amongst all the instances it is used in.
- To get instance specific descriptors, you set the value that you are changing to be assigned as a dictionary using the host instance as the key and the value, as the value. (More contorting!) and then return the value of the
[host_instance]
value. - Definitely tons of practice needed with this.
- Not used much, in day to day work, but used heavily behind the scenes by Python itself for its object system. The methods, Python classes have, are plain old functions, with descriptors (in conjunction with weak referenced elements) applied to them.
- The more I dive into all of this, the more Python feels less magical and more like the work of thousands of hard working atomic elements. And I think, I like this better. That everything complex, is actually lots of simple pieces, working together in concert.
- To fit the descriptor protocol, a descriptor class needs to implement…
** Part 5, Advanced Topics **
- The only thing I can write here, is that Reuven goes deep into hijacking how Python creates classes and then using them for our own customised ends. How to create my own custom
__new__
method, my own custom__init__
method, my own customtype
of object, create my own class with a metaclass… - Never going to use this, unless I am writing the next Django or something, and even then I doubt, these would be needed 😂
- But this was so much fun to learn!
Read all about my Python Objects journey here