Monday 13 May 2024

Static methods vs class methods in Python

 Background

In the last post, we saw how to work with classes in Python. We saw private methods, instance variables, class variables, etc. In this post, we are going to explore static and class methods in Python. If you are from a Java background you would use class methods or static methods interchangeably but that is not the case with Python. 


Class methods in Python

  • Class methods are your methods that are at class level (they belong to the class & not individual instances).
  • They will get the reference to the class as the implicit first argument (similar to how instance methods get a reference to self)
  • Class methods have access to class variables /state and can modify them
Consider the following  example:
class Employee:

    BASE_INCOME = 10000

    def __init__(self, name :str) -> None:
        self.name = name

    @classmethod
    def get_base_income_with_cls_method(cls) -> int:
        return cls.BASE_INCOME

    @classmethod
    def set_base_income_with_cls_method(cls, income: int) -> None:
        cls.BASE_INCOME = income


e = Employee("Aniket")
print(Employee.BASE_INCOME)
print(Employee.get_base_income_with_cls_method())
print(e.get_base_income_with_cls_method())
Employee.set_base_income_with_cls_method(100)
print(Employee.BASE_INCOME)
print(e.get_base_income_with_cls_method())
e.set_base_income_with_cls_method(10)
print(Employee.BASE_INCOME)
print(e.get_base_income_with_cls_method())


It prints:

10000
10000
10000
100
100
10
10

Notice

  • You declare a method as a class method using @classmethod decorator
  • The class method automatically gets an implicit argument cls which is a reference to the class.
  • BASE_INCOME is defined outside __init__ and hence is a class variable.
  • We have defined two class methods get_base_income_with_cls_method and set_base_income_with_cls_method which get and set that class variable (You actually do not need this and can do it directly with class reference E.g., Employee.BASE_INCOME=X etc. but have added to show how the class method has access to class variables via cls - 1st implicit argument).
  • Changes made to the state by class methods are reflected across all instances of the class.
This is what you would know as a static method in Java.

Now let's see what a static method in Python looks like:

Static methods in Python


  • A static method does not get an implicit first argument (as class methods did). 
  • Similar to class methods a static method is also bound to the class (and not the object of the class) however this method can’t access or modify the class state. 
  • It is simply present in a class because it makes sense for the method to be present in class (rather than at individual instance level, for eg., some utility methods).
  • You can create a static method using @staticmethod decorator.

Consider the following example:
class Employee:

    def __init__(self, name: str) -> None:
        self.name = name

    @staticmethod
    def is_same_name(name1: str, name2: str) -> bool:
        return name1 == name2


print(Employee.is_same_name("Aniket", "Abhijit"))
print(Employee.is_same_name("Aniket", "Aniket"))


It prints:

False
True

As you can see these can be used for simple utility methods.


When to use class methods and static methods?

  • Class methods are generally used to create factory methods. Factory methods return class objects depending on the usecase.
  • Static methods are generally used to create utility functions.




Related Links

Working with classes in Python

 Background

In the last few posts, we saw how Python works, what are closures, decorators, etc. In this post, I am going to explain how to write a class in Python and work with it. As you might already be aware though Python is used as a scripting language we can use it as an Object-oriented programming language.

Writing classes in Python

Let's start with a simple class and how to use it.

class Employee:
    def __init__(self, name: str, age: int) -> None:
        self.name = name
        self.age = age

    def __str__(self) -> str:
        return f"Employee with name {self.name} and age {self.age}"

e = Employee("Aniket", 32)
print(e)


The above code prints: Employee with name Aniket and age 32

Let's try to understand what the above code does.

  • First, we have defined a simple class Employee using a keyword class
  • Then we have defined two magic methods (We will see what magic methods are later, for now just imagine these are special methods that are created for some specific intended functionality, wherever you see a method starting with double underscores you know it is a magic method)
    • __init__ : __init__() method in Python is used to initialize objects of a class. It is also called a constructor. 
    • __str__: The __str__() method returns a human-readable, or informal, string representation of an object. This method is called by the built-in print() , str() , and format() functions. If you don't define a __str__() method for a class, then the built-in object implementation calls the __repr__() method instead.
  • Notice how the constructor takes 3 arguments
    • self: Every method of a class that is an instance method (& not a class/static method - more on this later) will always have self as 1st argument. It is a reference to the instance created itself (e instance in the case of above).
    • name & age: These are 2 new parameters that init takes which means when we want to create an instance of Emploee we need to pass name and age. These are our class instance variables.
    • Also, notice how we have provided hints for the type of arguments (E.g., the name being of str type) and return type of method (-> None). These are optional but good to have. These are called annotations and are used only as hints (Nothing is actually enforced at run time)
  • Lastly, we have just created an instance of Employee and printed it. Notice unlike Java you don't need to use a new keyword here. Since we defined our own implementation of __str__ it printed the same when we printed the object (Else it would have printed something like <__main__.Employee object at 0x000001CB20653050>).

Class variables and private methods

  • Unlike Java, we do not define the type of variables (It's dynamic). 
    • If you have declared variables inside __init__ using self then those are instance variables (specific to the instance you created).
    • If you declare variables outside __init__ then those belong to the class (are called class variables) and can be accessed as ClassName.variable.
  • Similarly, there are no access modifiers like Java
    • If you want a method or variable to be private just start it with a single underscore. 
      • E.g., _MY_PRIVATE_ID = "ABC"
      • Please note this is more of a convention and nothing is enforced at run time (similar to the hints annotations I explained above). If you see such variables/methods then you should use caution before you use them as public variables/methods.



Let's look at an example to understand class variables and private methods.
class Employee:
    _BASE_INCOME = 10000

    def __init__(self, name: str, age: int) -> None:
        self.name = name
        self.age = age
        self.income = None

    def _calculate_income(self) -> int:
        return Employee._BASE_INCOME * self.age

    def get_income(self) -> int:
        return self._calculate_income()


e = Employee("Aniket", 32)
print(Employee._BASE_INCOME)
print(e._BASE_INCOME)
print(e.get_income())
print(e._calculate_income())

Above prints:
10000
10000
320000
320000

A couple of points to note here

  • _BASE_INCOME since it is defined outside __init__ is a class variable. It can be accessed using class - Employee._BASE_INCOME. As you can see this starts with an underscore and is a private variable.
  • _calculate_income is a private method defined to calculate income based on _BASE_INCOME and age. There is a public method get_inome exposed that uses the private method to compute the actual income.
  • As you can see even though both variables and methods mentioned above are private you can access them. That is because as I mentioned before python does not enforce these, it's the developers' responsibility to use these with caution.

Now the natural question is how do we add validations to ensure that the instance variables are of the correct type. Let's see that next.


Using Properties

If you have worked on Java before you know that you can have getters and setter for instance variables. In Python, you can do the same using properties. See the below code for example:

class Employee:
    _MAX_AGE = 150

    def __init__(self, age: int) -> None:
        self._age = None
        self.age = age

    @property
    def age(self) -> int:
        return self._age

    @age.setter
    def age(self, value: int) -> None:
        if value > Employee._MAX_AGE:
            raise ValueError(f"Age cannot be more that {Employee._MAX_AGE}, Passed: {value}")
        self._age = value


e = Employee(200)


This prints:

Traceback (most recent call last):
...
  File "C:\Users\Computer\Documents\python\test.py", line 15, in age
    raise ValueError(f"Age cannot be more that {Employee._MAX_AGE}, Passed: {value}")
ValueError: Age cannot be more that 150, Passed: 200

Process finished with exit code 1

Notice
  • How we are using a corresponding private variable(_name) to track the actual value stored internally and using the name as an interface to be used publically.
  • When you try to create an Employee instance by passing age as 200 it actually calls the setter method which checks it is more than 200 and throws a ValueError.

Lastly, let's see a compact way to write the classes using dataclass decorator.

Using the dataclass decorator

You can use the dataclass decorator to write a class compactly. See the following example.

from dataclasses import dataclass, field


@dataclass
class Employee:
    name: str
    age: int
    BASE_INCOME: int = field(init=False, repr=False)


e = Employee("Aniket", 33)
print(e)

Above prints: Employee(name='Aniket', age=33)

Note here

  • Notice the import of dataclass
  • Once you decorate your class with @dataclass you get a few things for free
    • It automatically creates instance variables with variables defined with annotation (name and age in the above case)
    • You do not need to define an __init__ method for this.
    • If you do not provide annotation hints then it automatically considers it as class variables.
    • If you want to use hints annotations and still want it to be a class variable then you can initialize it with the field method by passing init and repr arguments as False. Eg., in the above case BASE_INCOME is not a class variable and will not be used in init or repr mafic methods.

Related Links

Monday 8 April 2024

Installing Python & related tools on Windows

Background

In this post, we will see how we can install Python & related tools on the Windows Operating System. 


Installing Python

Let's start with installing Python itself. For this, you can go to the Python official website and download the Windows installer (link). The version at the time I am writing this is Python 3.12.2. You can download the installer based on your Windows architecture. I have Windows 64-bit, so I downloaded the corresponding installer.


You can download and execute the installer. It will install Python and add it to your PATH which will let you execute the Python command on the cmd console or powershell terminal. You can check the Python version by following the command
  • "py --version"
You can also start the py console by just typing "py" in the console.



You can also create a python file with the extension ".py", add python code, and execute it with the command
  • py filename.py




Installing other tools

It is important we install a few other tools to work efficiently with Python.

Pycharm

Let's start with installing IDE. For any programming language, it's important we install an IDE to improve the convenience of use & productivity. I prefer using Pycharm but you could use other alternatives like Eclipse.

You can install the community version of Pycharm from their official site (link). You can scroll down to download and install the community version which is free to use. You can use inbuilt tools like create python project/file, run code, etc. to build and run your code.


Jupyter Notebook

Next, I would recommend installing Jupiter Notebook. You can use Anaconda distribution to use it (Just install the Anaconda installer and you can use a notebook from it). You can download the installer from here. You can use it as mentioned here. It helps execute code snippets for quick testing.





Recommended links



Friday 29 December 2023

Understanding Rest & Spread operator in Javascript

 Background

In JavaScript, we tend to use rest & spread operators to keep code simple and easy to read. Both use three dots "..." but the use cases are different. In this post, we will see what these operations are and how to use them.


Before we proceed to looking into individual aspects just remember rest is for collecting elements and spread is for spreading elements. 

Rest operator

Let's try to see the rest operators 1st. The rest operator is used to collect the remaining elements in an array. The rest syntax works in both function arguments and arrays. Let's look at an example below



As you can see whatever number of arguments you pass to add function is going to capture 1st two in a & b respectively (in this case 1 & 2) and the remaining ones (values - [3, 4]) will be captured in an argument called rest which is an array.

The rest operator is also used for destructuring arrays like below

Spread operator

The Spread operator introduced in ES6 as the name suggests is used to spread the elements.
For eg, in the above code, you can see how "123" was split automatically into 1,2 & 3 by the spread operator.

We can also use this operator to easily create copies of arrays and objects. For eg.,



 NOTE: Spread operator helps maintain immutability requirements. This is helpful when you need data that should not mutate (Eg. Redux state).

As I mentioned before rest and spread operators are used commonly in javascript and we will see more usages and examples in upcoming posts.

Related Links




Closures in Javascript

 Background

In one of the earlier posts, we saw how to use Javascript (JS) closures to hide variables from the console with a counter-example. In this post, I will explain JS closures in more detail.

Closures in Javascript

Quoting closures from MDN web docs:

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

It's important to note that it is not just a function but a combination of function + referenced to its lexical environment.  

Let's take a simple example:

(() => {
    var name = "Aniket";
    const displayName = () => {
        console.log(name);
    }
    displayName();
})()

The (()=>{})() is a self-executing function. Inside it, you can see an anonymous function defined using the arrow operator. Inside this function, we have defined a variable called name and again an anonymous function called displayName which prints the name variable in the console. 

What is interesting to note here is that even though the variable name is local to the outermost anonymous function we can still access it inside the displayName method, this is possible due to closures. When we created an anonymous function & assigned it to the displayName const variable it actually created a closure that comprised of the function itself + the reference to the lexical environment which includes the name variable defined in the outer scope.

NOTE: Nested functions have access to variables declared in their outer scope.

Understanding scopes

In the above examples, the variable name was defined in a function and had functional scope. It was accessible in that function + the methods nested inside it. It will not be accessible in the global scope. 

Before ES6 there were just two scopes, function scope and global scope. variables defined inside a function had function scope where whereas those defined outside had global scopes. This came with its own challenges as now if you define variables inside if else block braces then it creates a global scope because blocks with curly braces do not create scopes.

Eg. see below

Here check variable even though was defined inside the if block has become a global scoped variable (Unlike other languages like Java where it would have created a local scoped variable). 

To handle this ES6 introduced two new declarations
  • let - defines a variable local to the scope
  • const - defines a constant variable again local to the scope.
See below for example:





NOTE: Closure gives the function access to its outer function but the outer function can also be a nested function inside another outer function. In such cases, the innermost function will have access to all the outer nested functions via the closure chain. See eg below



Interesting examples

Now let's see some interesting examples before we wind this up.

See the below examples & predict the output

const myName = ["Aniket", "Abhijit", "Awantika"];
for(var i; i<myName.length;i++)
{
	setTimeout(function(){
    	console.log('Name: ' + myName[i] + 'at index:' + i);
    },3000)
}

The output is


It prints undefined as Name & index as 3 all the three times the loop executes. Let's try to understand why this happened: When we pass a custom function inside setTimeout it creates a closure of the function bundled with the captured environment from the out functions scope. Three closures are created by the loop but each shares the same lexical environment which has the variable "i" with changing values. This is because the "i" variable is defined as var and has a global scope. The value of "i" in the passed function is determined when it is actually executed after the timeout completes (when it comes from the event loop). Since the loop has already been completed by the time these closures from the event loop are executed "i" points to 3 and there is no such index in that array as the size is 3 and the index range from 0 to 2. Hence it prints "i" as 3 and the name as "undefined" three times - one for each closure created in the loop.

You can fix this by using a wrapper function to be passed inside setTimeout API argument which will create its own closure with the actual local value of "i" and pass it to the event table instead of just the original closure which is pointing to global scoped "I". Eg see below where I have given two ways one using function keyword and one using anonymous function



Or you could simply change var to let in the for loop which will create a block-scoped variable instead of a global scope and changes will still work fine.



Related Links

t> UA-39527780-1 back to top