Kartones Blog

Be the change you want to see in this world

Object Oriented Python - A Summary

I love Python, but you can notice how object orientation was an afterthought (with some wanky compiler tricks) when you learn how its object-oriented system works. It is powerful, but some concepts hard to grasp, and not always properly explained out there. Hence, the main reasoning behind writing this post.

It is not an exhaustive guide, but I think I've touched the main points and should be a decent enough quick reference guide.

Basic definitions

  • Class: What we define. Kind of the structure of which we'll create instances of. Contains methods, attributes and properties.
  • Instance: Also commonly to what we refer when we say Object. A specific entity that has everything that a certain Class defines, with it's own values/state. One class can have many instances.
  • Simple Inheritance: When a class has only a single parent class. A class inherits methods, attributes, properties, etcetera from the parent class.
  • Multiple Inheritance: When a class has more than one parent class. Python supports multiple inheritance.
  • Attribute: A variable a class defines, and thus, that every instance will contain, each with a different value. An attribute might be public, protected or private.
  • Class Attribute: A variable that exists on the class itself, and thus is shared (and accessible) by all instances (if any).
  • Property: A function or functions (getter and/or setter), that wrap an attribute and allow to also add logic around operating with it, while keeping the usage syntax same as if using an attribute (e.g. my instance.an_attribute -> my_instance.a_property )
  • Method: A function defined inside a class. Same as attributes, can be marked as protected or private, but really all are public. More on methods later.
  • Constant: Python has no constants, but by convention a variable or class attribute that is all uppercased is considered a constant.
  • Abstract classes and abstract methods: Python got support for abstract classes and methods late in its life, so you need to use the abc base package. This tutorial won't cover them as they are not very widely used, but if you want info, check the official documentation for ABCs.

Class creation basics

class Person:
  A_CONSTANT = 33

  a_class_attribute = 'Something'

  # Class constructor with two parameters (`self` is always required)
  def __init__(self, name, address):
    # private instance attribute
    self.__name = name
    # public instance attribute
    self.address = address

  # getter, to do for example: print(myInstance.name)
  @property
  def name(self):
    return self.__name

  # setter, to do for example: myInstance.name = 'New name'
  @name.setter
  def name(self, value):
    self.__name = value

  # `self` is always required for normal class methods
  def a_method(self, a_parameter):
    # note that despite being a class attribute, we can also access `a_class_attribute` through `self`
    print(`{name} {attr} {param}`.format(name=self.name, attr=self.a_class_attribute, param=a_parameter))

Creating new instances of a class is also pretty simple:

a_person = Person('John Doe', 'somewhere')
another_person = Person('Jane Doe', 'somewhere else')

In python we signal with _ protected methods and attributes, and if we "really" want to make them private, we can use __ (two underscores) and, when compiling the class, the Python interpreter will change the attribute name so outside callers can't (so easily) access them.

Remarks

There is also a property deleter, but it is not commonly used.

Do not rely on scope modifiers for security, just for explaining to users of your class the intended interface (what you expose for external usage). You can always access protected attributes and methods, and with a little extra work also the private ones.

You will often find classes simply exposing attributes instead of using properties. Properties can also be added using special functions, but the decorators are cleaner, easier and preferred.

You can dynamically add new attributes anywhere from a class, and in fact you will see this behaviour at many professional and open-source projects. However, a good convention for future code readers is defining all of them inside the constructor.

Class vs Static vs Instance variables and methods

In Python, there are really two state-related scopes for variables and methods: class/static and instance. A variable either lives at each and every instance of a class, or then it lives as a class variable and is shared among all instances; and same goes with methods.

class Person:
  a_class_attribute = 'something'

  def __init__(self):
    self.an_instance_attribute = 'something else'

  @staticmethod
  def a_static_method(a_param):
    print(Person.a_class_attribute, a_param)

  def an_instance_method(self, a_param):
    print(self.an_instance_attribute, a_param)

But when writing classes, we can actually define methods in a third way, as a "class method". This is mostly syntactic sugar, created in my opinion to help you write clean code, and I'll explain why after explaining what it is.

A class method is defined with the @classmethod decorator, and does have an additional first "magic" parameter like self, in this case cls. That parameter allows us to call to the class itself, without repeating its name; other than that, works exactly the same as a @staticmethod:

class Person:
  a_class_attribute = 'something'

  @classmethod
  def a_static_method(cls, a_param):
    print(cls.a_class_attribute, a_param)

Now, what's the point of having @classmethod if does the same as @staticmethod but with more magic? Well, the intention is to write code Python style, by adhering to the following rules when writing methods for your classes:

  • If you need to use or alter instance variables, you need self so it is an instance method
  • If you need to use or alter class variables, you should use cls and @classmethod
  • If neither of the above, then you should use @staticmethod

Let's rewrite and expand Person applying the rules:

class Person:
  a_class_attribute = 'something'

  def __init__(self):
    self.an_instance_attribute = 'something else'

  @staticmethod
  def a_static_method(a_param):
    print(a_param)

  @classmethod
  def another_static_method(cls, a_param):
    print(cls.a_class_attribute, a_param)

  def an_instance_method(self, a_param):
    print(self.an_instance_attribute, a_param)

Using cls when calling static stuff of the own class means you can easily refactor its name without needing to grep the code. There should be a single occurrence in the class definition file, and most IDEs will color and hint and the like the same as using Person.xxxxx.

As I like to sometimes go into the internals of things, to help hold better the concepts, the following snippet shows how you would call each method, and how Python really calls them, which I think it's interesting and revealing:

a_person = Person()

# how you'll call it:
a_person.an_instance_method('A')
# how python calls it:
Person.an_instance_method(a_person, 'A')

# you:
a_person.a_static_method('B')
Person.a_static_method('B')
# python:
Person.a_static_method('B')

# you:
a_person.another_static_method('C')
Person.another_static_method('C')
# python:
Person.another_static_method(Person, 'C')

As we just saw, objects really behave like static classes to which Python passes the instance to operate with. A clever trick but confusing at first nonetheless.

Remarks

You can always use self. from instance methods to refer to class attributes, just take into account that it will first check instance attributes, then class attributes.

You can also overload operators and certain special methods (comparators, string conversion, etcetera).

Inheritance

class A:
  def __init__(self, something):
    pass

  def a_method(self):
    pass

class B(A):
  def __init__(self, something):
    super().__init__(something)

  def a_method(self):
    super().a_method()

class C:
  def __init__(self, something):
    pass

  def a_method(self):
    pass

  def another_method(self):
    pass


class D(B, C):
  def __init__(self, something):
    # calls B's constructor
    super().__init__(something)

  def a_method(self):
    # calls B's a_method
    super().a_method()

To call a parent class method, you use super(). In the example above, we use it to call A's constructor from B's, passing the something parameter (pretty useless example, I know, there are way better ones out there).

What happens when you have multiple-inheritance and two or more parent classes have the same method overloaded? In short, python's method resolution order works from left to right regarding how you defined the parent classes: in the previous example B's a_method takes precedence over C's a_method if D did something like super().a_method() somewhere.

Method Overloading

Python does not support method overload, meaning that you can only have one a_method() inside a given class. In other languages, like for example C#, you could have one overload with 2 parameters, another with 3, etcetera. Not in Python.

What you can do is override a parent class method inside a child class. We already saw this in the previous section.

Closing thoughts

This was a brief introduction to Python's OOP. If you wish to really get into the details, I strongly recommend visiting the website realpython.com, which contains many very detailed and nicely explained articles about most of the language features.


Course Review: Monorepos - A Beginner's Guide (Udemy)

Review

Monorepos: A Beginner's Guide is a ~40 minutes 2020 small course that, despite it's catchy but generic name, really should be titled something like "Multi-package and Monorepo NodeJS projects using Yarn".

The topics covered are:

  • Now NodeJS searches for and loads modules/packages
  • Using yarn link
  • Using yarn workspaces
  • package hoisting when using workspaces
  • cross-package bin scripts
  • Using yarn workspace
  • A bonus 10 minutes lesson about lerna, covering the basic commands, scoped packages and npm publishing of monorepo packages using the tool

Although you can apply the same principles and actions with npm 7+, all examples are explained and performed via yarn.

It is a very compact course, very very focused on node + yarn, but if that's what you search for, it is concise, direct to the point and for once, quicker than reading through official documentation.

My only concern is that the initial approach of manually placing modules inside node_modules folder, although if mentioned as a sub-optimal solution, it is a really ugly workaround 😵‍💫 (the author even provides instructions to properly un-gitignore and commit the hack!)


Monorepo-related resources

I am doing some research regarding Monorepos, and I thought could be interesting to leave here a list with most of the public articles, talks and papers I'm going through. And update it whenever proceeds.

Sorted alphabetically and with the year or an approximation of it (e.g. when was last updated) to clearly see the freshness of the content.

If you think I missed any additional resource, please drop me a tweet with a link so that I can take a look at it.

Reading

  • 11 Great Tools for a Monorepo in 2021 (2020): article
  • Advantages and Disadvantages of a Monolithic Repository: A case study at Google (2018): paper
  • Advantages of monorepos (2015): article
  • Angular loves Bazel leaving Angular Labs (2020): article
  • Architecting a Modern Monorepo with NX and Turborepo (2022): article
  • Atlassian: Monorepos in Git (~2016): article
  • Awesome Monorepo: Good Reads (2021): article
  • Bazel Hermetic Toolchain and Tooling Migration (Tinder) (2022): article
  • Bring your monorepo down to size with sparse-checkout (2020): article
  • Building Uber’s Go Monorepo with Bazel (2020): article
  • Continuous Integration for Monorepos (2021): article
  • Dropbox: Continuous integration and deployment with Bazel (2019): article, interview
  • Git's feature.manyFiles, --untracked-cache and Large File Storage
  • Goodbye Microservices: From 100s of problem children to 1 superstar - Segment.com (2018): article
  • Guide to Monorepos for Front-end Code (~ 2018): article
  • How Git Partial Clone lets you fetch only the large file you need (2020): article
  • Microsoft: Evolving a large Typescript repository (2019): article
  • Microsoft Scalar Git tools & extensions: repository
  • Monorepo.tools: Explanation, benefits and main tools comparison (2022): article & comparison
  • Monorepo is a bad idea (2020): article
  • Monorepos done right (2022): article
  • Monorepos in JavaScript, Anti-Pattern (2020): article
  • Monorepos in JavaScript & TypeScript (2022): article
  • Monorepos make inner-source come to life (2020): article
  • Segment: Goodbye Microservices: From 100s of problem children to 1 superstar (2018): article
  • SemaphoreCI: CICD for Monorepos (2021): ebook
  • Some reasons why Google Reader wasn't open-sourced (2014): article
  • What is monorepo? (and should you use it?) (2021): article
  • Why Google Stores Billions of Lines of Code in a Single Repository (2016): paper
  • Why you should use a single repository for all your company’s projects (2016): article

Watching

  • Big Code: Developer Infrastructure at Facebook's Scale (2015): talk
  • From Monorail to Monorepo: Airbnb’s journey into Microservices (2018): talk
  • Microsoft: Developing in a large monorepo (2019): talk
  • Monorepos - A Beginner's Guide (2020): paid course
  • Pinterest’s journey to a Bazel monorepo (2020): talk
  • Uber Technology Day: Monorepo to Multirepo and Back Again (2017): talk
  • Why Google Stores Billions of Lines of Code in a Single Repository (2015): talk
  • Why you need a build system, and why it should be Bazel - Google/Angular (2018): talk

Course Review: TypeScript Essential Training (LinkedIn Learning)

Review

I decided to also do the TypeScript Essential Training course because the author is the same as Learning Typescript and recommended this one as a more complete and detailed dive-in. My review considers that same path and judges accordingly the contents of the present course.

This is an intermediate training of more than 4 hours, divided into 9 chapters. The first four ones are basic setups, a basic introduction of Typescript and typing, and quite a few ES2015 concepts. As such, if you already know some basics and/or are up to date with ES2015 or ES6, I suggest you to directly skip the first 4 chapters and start from the fifth one. Still, the remaining chapters cover in good detail not only main features but also more advanced topics like function overloads, abstract classes, generic constraints, namespaces, sourcemaps... The examples presented are more akin to real world scenarios and the sample application (a TODO webapp) is small yet appealing to see how to migrate and add typing.

The explanations of how Typescript compiles to Javascript certain features are sometimes very interesting, like namespaces being implemented using IIFEs or visibility access modifiers not really being enforced at runtime. As the course is from 2016, the ECMAScript version was yet ES2015, and even some features available on that version are not always present in the sample code, but in general I still found the contents useful.

All in all, I think I extracted quite a few learnings from the course.


Course Review: Learning Typescript (LinkedIn Learning)

Review

Slightly above an hour in duration, Learning Typescript will nicely guide you through the basic drills of setting up Typescript and adding it to an existing project, alongside explaining the basics: types, interfaces, classes, enums, literal types, a small introduction to generics and type declarations. It can’t go in depth but I was pleasantly surprised of how focused it is, perfect to get a general idea.

Oh, and it even manages to mention some cool tools and projects like DefinitelyTyped, for those third party libraries you might be using that don't provide type definitions yet.


Previous entries