Skip to main content

Python Primer from Typescript

Python Primer for Typescript Developers

Python and TypeScript are both popular programming languages used for various applications. While TypeScript is a superset of JavaScript with added static typing, Python is a high-level, dynamically-typed language known for its simplicity and readability. This primer aims to help experienced TypeScript developers understand the key concepts and differences when working with Python.

1. Variables and Data Types

In TypeScript, you declare variables with specific types:

let age: number = 25;
let name: string = "John";
let isStudent: boolean = true;

In Python, you don't need to specify types explicitly:

age = 25
name = "John"
is_student = True

2. Functions

TypeScript functions have type annotations for parameters and return values:

function greet(name: string): string {
    return `Hello, ${name}!`;
}

Python functions are defined using the def keyword:

def greet(name):
    return f"Hello, {name}!"

3. Arrays and Lists

TypeScript has typed arrays:

let numbers: number[] = [1, 2, 3, 4, 5];

Python has lists, which are similar to arrays but can hold elements of different types:

numbers = [1, 2, 3, 4, 5]

4. Objects and Dictionaries

TypeScript has interfaces and object types:

interface Person {
    name: string;
    age: number;
}
let person: Person = {
    name: "Alice",
    age: 30
};

Python has dictionaries, which are key-value pairs:

person = {
    "name": "Alice",
    "age": 30
}

5. Classes

TypeScript supports classes with constructors, properties, and methods:

class Rectangle {
    private width: number;
   private height: number;
   static help(): void {
         console.log("This is a rectangle class");
   }
   constructor(private width: number, private height: number) {
         this.width = width;
         this.height = height;
         this.getArea = this.getArea.bind(this);
    }
    getArea(): number {
        return this.width * this.height;
    }
}

Python classes are defined using the class keyword. Let's define a similar class in Python:

class Rectangle:
    
    def __init__(self, width, height):
        self.width = width
        self.height = height
    def get_area(self):
        return self.width * self.height
   @staticmethod 
   def help():
        print("This is a rectangle class")

Notice that __init__ is the constructor in Python, and self is used to refer to the instance of the class. Also notice that there are no need to declare properties in the class definition, rather they are "dynamically" added to the instance of the class via the __init__ method.

  • Note that the TypeScript class has a private modifier for the properties, while Python does not have such a modifier.
  • Note that the TypeScript class has a method getArea that is bound to the instance of the class, while in Python, the method get_area is not bound to the instance of the class.
  • Note that the TypeScript class has a return type annotation for the getArea method, while Python does not have such annotations.
  • Note that the TypeScript class has a static method help, while Python does not have such a concept. In Python, you can define a static method by using the @staticmethod decorator, which is similar to the static keyword in TypeScript.

Decorators

Typescript does not have a direct equivalent to decorators in Python. In Python, decorators are used to modify the behavior of functions or methods. In TypeScript, decorators are used to add metadata to classes, methods, and properties. Decorators in TypeScript are similar to annotations in Python.

function log(target: any, key: string) {
    console.log(`${key} was called`);
}  
class MyClass {
    @log // This is a decorator
    method() {
        console.log("Method called");
    }
}

In the above example, the log decorator is applied to the method of the MyClass class. When the method is called, the decorator logs the name of the method to the console. In Python, decorators are used to modify the behavior of functions or methods.

def log(func):
    def wrapper(*args, **kwargs):
        print(f"{func.__name__} was called")
        return func(*args, **kwargs)
    return wrapper
class MyClass:
      @log # This is a decorator
      def method(self):
         print("Method called")

Its important to note that decorators in Typescript are completely different in purpose and usage from decorators in Python, even though they modify some behavior and use a similar syntax. In Python, decorators are used to modify the behavior of functions or methods, while in TypeScript, decorators are used to add metadata to classes, methods, and properties.

Annotations

TypeScript uses type annotations to specify the type of a variable, parameter, or return value:

function add(a: number, b: number): number {
    return a + b;
}

Annotations above would be the : number part of the parameters and return value. Python uses type hints to specify the type of a variable, parameter, or return value:

def add(a: int, b: int) -> int:
    return a + b

The -> int part of the function definition is the type hint for the return value. Note that the arrow is not used in parameter type hints in Python. This is to differentiate between the return type and the parameter types. Why does Typescript not differentiate between the return type and the parameter types? It's not necessary given that Typescript is by nature a statically typed language versus Python which is dynamically typed (hence Typescript does not require further need to clarify annoations that would be needed when things are more prone to ambiguity in dynamically typed languages. Note: Javascript is dynamically typed, hence the need for Typescript to add static typing to Javascript. Note: Python does have a static typing language called mypy, which can be used to add static typing to Python).

## Example of how the add function can be further statically typed using mypy
def add(a: int, b: int) -> int:
    return a + b


The above code is an example of how you would use mypy to add static typing to Python, note how it is the same as the normal Python code, but with the addition of the type hints in the function definition. Essentially, mypy is the equivalent of Typescript for Python, and is supported by default in Python 3.6 and above. To use it in lower versions of Python, you would need to install it using pip. And then import it in your code using import mypy.

6. Modules

TypeScript uses ES6 module syntax for importing and exporting:

import { someFunction } from "./module";
export const PI = 3.14;

Python uses the import keyword for importing modules and from for specific imports:

from module import some_function
PI = 3.14

7. Iteration and Loops

TypeScript has for and while loops for iteration:

for (const num of numbers) {
    console.log(num);
}
let i = 0;
while (i < numbers.length) {
    console.log(numbers[i]);
    i++;
}

Python has for and while loops as well:

for num in numbers:
    print(num)
i = 0
while i < len(numbers):
    print(numbers[i])
    i += 1

8. Regular Expressions

TypeScript has built-in support for regular expressions:

To match a pattern:

const pattern = /\d+/;
const result = "123abc".match(pattern);
// result: ["123"]
import re
pattern = r"\d+"
result = re.findall(pattern, "123abc")
# result: ["123"]

To capture groups:

const pattern = /(\d+)(\w+)/;
const result = "123abc".match(pattern);
// result: ["123a", "123", "a"]
pattern = r"(\d+)(\w+)"
result = re.match(pattern, "123abc")
# result: ["123a", "123", "a"]

To match multiple occurrences:

const pattern = /\d+/g;
const result = "123abc456".match(pattern);
// [ ["123"], ["456"] ]
pattern = r"\d+"
result = re.findall(pattern, "123abc456")
# result: ["123", "456"]

9. Error Handling

TypeScript uses try, catch, and finally for error handling:

try {
    throw new Error("Something went wrong");
} catch (error) {
    console.error(error.message);
} finally {
    console.log("Cleanup");
}

Python uses try, except, and finally for error handling:

try:
    raise Exception("Something went wrong")
except Exception as error:
    print(error)
finally:
    print("Cleanup")

11. JSON Serialization

TypeScript has built-in JSON support:

const person = { name: "Alice", age: 30 };
const json = JSON.stringify(person);
const jsonPretty = JSON.stringify(person, null, 4);
// json: '{"name":"Alice","age":30}'
// To parse JSON:
const personObj = JSON.parse(json);

Python has built-in JSON support as well:

import json
person = {"name": "Alice", "age": 30}
json_str = json.dumps(person)
# json_str: '{"name": "Alice", "age": 30}'
## you can alos pretty print the json string
json_str = json.dumps(person, indent=4)

# To parse JSON:
person_obj = json.loads(json_str)

## you can also use the json module to write json to a file
with open("person.json", "w") as f:
    json.dump(person, f)

12. File I/O

TypeScript does not have built-in file I/O support, but it can be achieved using Node.js APIs. Python has built-in file I/O support:

import * as fs from "fs";
fs.readFile("file.txt", "utf8", (err, data) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log(data);
});
with open("file.txt", "r") as file:
    data = file.read()
    ## So file is closed here automatically
    print(data)

Note the with statement, which does not exist in TypeScript. It is used in Python to ensure that the file is properly closed after reading. Without using with, you would then need to call file.close() explicitly at some point.

  • Note how there is a as file: portion, which does not really exist in TypeScript. This is used to create a variable file that represents the file object. This is not necessary in TypeScript. In typescript you would just write const file = fs.readFileSync("file.txt", "utf8") to read the file.
  • In python having as <something>: is used to create an alias. Typescript does not have this feature, why? Because in typescript you can just create a variable with the name you want to use as an alias.
  • Another example of this kind of odd with and aliasing in python outside of files is in the import statement. In python you can write import module as alias to import a module and give it an alias. In typescript you would just write import * as alias from "module".

13. Promises and Asynchronous Programming

TypeScript has asynchronous programming using Promises and async/await:

function fetchData(): Promise<string> {
    return new Promise((resolve, reject) => {
         resolve("Data fetched");
    });
}
async function getData() {
    const data = await fetchData();
    console.log(data);
}
getData();

Python has asynchronous programming but does not have Promises. Instead, it uses async and await with coroutines:

import asyncio
async def fetch_data():
    return "Data fetched"
async def get_data():
      data = await fetch_data()
      print(data)
asyncio.run(get_data())

Essentially, async declarations in python are simply the same as a promise in typescript. The await keyword in python is used to wait for the result of the promise.

14. Callbacks in Asynchronous Programming

TypeScript uses callbacks for asynchronous programming:

function fetchData(callback: (data: string) => void) {
    setTimeout(() => {
        callback("Data fetched");
    }, 1000);
}
function getData() {
    fetchData((data) => {
        console.log(data);
    });
}
getData();

Python uses callbacks as well:

import time

def fetch_data(callback):
    time.sleep(1)
    callback("Data fetched")

def get_data():
      fetch_data(lambda data: print(data))

get_data()

Note the lambda keyword, which is used to define anonymous functions in Python. In TypeScript, you would use arrow functions for the same purpose:

fetchData((data) => {
    console.log(data);
});

Recall that arrow functions in Typescript differ from normal functions in that they do not have their own this context, and hence are used to define functions that do not need to access the this context of the parent function. In Python, lambda functions are used to define functions that are not needed to be defined in a separate function definition. If you try to access this in an arrow function in Typescript, it will refer to the this context of the parent function. In Python, you would use the self keyword to refer to the this context of the parent function.

  • The benefit to using lambda/arrow functions is the function is created in place which means you don't have to define a function elsewhere in your code, which can be useful for simple functions that are only used once. Not having to define a function for simple code helps to keep your code clean and concise, and also saves space in your code due to the extra space required when defining a function.
  • The disadvantage to using lambda/arrow functions is that they can make your code harder to read and understand, especially if the function is complex. This is because lambda/arrow functions are defined in place, which means you have to read the function definition in the same place where it is used. This can make it harder to understand the code, especially if the function is complex or if it is used in multiple places in your code.
    • Another disadvantage to using lambda/arrow functions is that they can make your code harder to debug. This is because lambda/arrow functions are defined in place, which means you can't easily see the function definition when you are debugging your code. This can make it harder to understand what the function is doing, especially if the function is complex or if it is used in multiple places in your code.
  • In typescript you can actually declare an arrow function like a function by writing const myFunction = function() {} which is not possible in Python. This is because in typescript, arrow functions are just a shorthand for defining functions, while in Python, lambda functions are a separate type of function that is used for a specific purpose, and are not meant to be used as a shorthand for defining functions.

16. Printing to Console

TypeScript uses console.log for printing to the console:

console.log("Hello, World!");

Python uses print for printing to the console:

print("Hello, World!")

Adding styles, colors, and formatting to console output:

TypeScript does not have built-in support for styling console output. Python has built-in support for styling console output using ANSI escape codes:

print("\033[1;32;40m Bright Green  \n")

17. JSDoc and Type Annotations Equivalents and Comments

TypeScript uses JSDoc comments for type annotations and documentation:

/**
 * Adds two numbers.
 * @param {number} a - The first number.
 * @param {number} b - The second number.
 * @returns {number} The sum of the two numbers.
 */
function add(a, b) {
    return a + b;
}

Python uses docstrings for documentation and type hints for type annotations:

def add(a: int, b: int) -> int:
    """Adds two numbers.
    :param a: The first number.
    :param b: The second number.
    :return: The sum of the two numbers.
    :example:
     >>> add(1, 2)
    """
    return a + b

And hence the comments in the above code are equivalent to the following docstring:

 (function) def add(
    a: int,
    b: int
    ) -> int
    Adds two numbers.
      :param a: The first number.
      :param b: The second number.
      :return: The sum of the two numbers. 
      :example:
        >>> add(1, 2)

Note how comments in python are written after the def keyword and before the function body. In typescript, comments are written before the function body. Why? Because in typescript, the function signature is written in the comments, while in python, the function signature is written in the function definition. Why the difference? Because in typescript, the function signature is not part of the function definition, while in python, the function signature is part of the function definition.

Comments

TypeScript uses // for single-line comments and /* */ for multi-line comments:

// This is a single-line comment
/*
This is a
multi-line comment
*/

Python uses # for single-line comments and ''' ''' for multi-line comments:

# This is a single-line comment
'''
This is a
multi-line comment
'''

18. Keywords unique Python

Python has some unique keywords that are not present in TypeScript:

yield

The yield keyword is used in Python to create a generator function. A generator function is a special type of function that can pause its execution and return a value to the caller without losing its state. This allows the function to resume its execution from where it left off when called again. Generators are useful for creating iterators and working with large datasets.

def count_up_to(n):
    i = 1
    while i <= n:
        yield i 
        i += 1

When yield i is called, the while loop pauses, and the count_up_to function returns the value of i to the caller. The loop can be resumed by calling the function again, which is useful for preserving the state of iteration. In Python, a dynamically typed language, there is less control over memory compared to statically typed languages like TypeScript. For a given n, the size may be too large, but since there is no way to declare the parameter type, yield is the next best solution. It allows processing data in smaller chunks to avoid potential memory issues. An equivalent handling in TypeScript would be .on('data') in Node.js, which enables processing data in chunks when the size of the streamed data is unknown, making it better to process in smaller portions.

yield based functions return not the data but a generator object. To then access the goal of the data, utilize the next() method on the generator object.

def count_up_to(n):
    i = 1
    while i <= n:
        yield i 
        i += 1

counter = count_up_to(5)
print(next(counter)) # 1

In case you get a "StopIteration" error, this error means that the generator has no data to yield, and thus your function may not need to utilize yield in its form of processing data given the lack of yielding shown. Remember, yield means the program will pause and return the value to the caller, and then resume from where it left off. In cases where this is done and not actaully need can result in the program not at all having the data or even processing it correctly: think of it in terms of handling code that isn't asynchrnous as if it were asynchronous, in some rare cases it may actually cause some odd issues dpeendent on code and environment.

def count_up_to(n):
    i = 1
    while i <= n:
        i += 1
        return i

yield from

Different from yield, yield from is used to delegate the iteration to another generator. This is useful when you have a generator that yields another generator. The yield from keyword allows you to yield values from the inner generator directly to the outer generator. This simplifies the code and makes it easier to work with nested generators.

def count_up_to(n):
    i = 1
    while i <= n:
        yield i 
        i += 1

def count_up_to_10():
      ## Thus, this allows a way to have nested yields. The depth of nesting is not limited to one level, but can be as deep as needed, and utilizes `yield from` on a `yield from` for a nest-within-nest yield.
      yield from count_up_to(10)

def nested_nested_yield():
    yield from count_up_to_10()

The benefit of nested yield is that it allows one to process a two-dimensional piece of data in chunks, rather than being limited to an array with just yield. Two-dimesnional data is very common, such as with images, sound, or video data. The yield from keyword allows you to process this data in chunks, which can be more efficient than processing it all at once. In TypeScript, you would have to use a library like RxJS to achieve the same functionality, which can be more complex and less efficient than using yield from in Python.

import { from } from 'rxjs';
import { map } from 'rxjs/operators';

const countUpTo = (n: number) => {
    let i = 1;
    return from(new Array(n).fill(0)).pipe(
        map(() => i++)
    );
};

const countUpTo10 = () => {
    return countUpTo(10);
};

const nestedNestedYield = () => {
    return countUpTo10();
};

del

The del keyword is used in Python to delete a variable or an item from a list or dictionary. This is useful when you want to remove a variable or an item from memory to free up space. The del keyword is not present in TypeScript, as TypeScript has automatic garbage collection, which means that variables are automatically deleted when they are no longer needed. In Python, the del keyword is used to manually delete variables or items from memory, which can be useful when you want to free up space or remove unwanted data.

x = 5
del x

In the above example, the variable x is deleted using the del keyword, which removes it from memory. This can be useful when you want to free up space or remove unwanted data from memory. In TypeScript, you would not need to use the del keyword, as variables are automatically deleted when they are no longer needed.

pass

The pass keyword is used in Python to create a placeholder for code that does nothing. This is useful when you want to create a function or a class that does nothing but needs to be defined for syntactic reasons. The pass keyword is not present in TypeScript, as TypeScript does not require a placeholder for code that does nothing. In Python, the pass keyword is used to create a placeholder for code that does nothing, which can be useful when you want to define a function or a class that does nothing but needs to be defined for syntactic reasons.

def my_function():
    pass

In the above example, the pass keyword is used to define a function my_function that does nothing. This can be useful when you want to create a placeholder for a function that does nothing but needs to be defined for syntactic reasons. In TypeScript, you would not need to use the pass keyword, as functions are defined without the need for a placeholder for code that does nothing.

global

The global keyword is used in Python to declare a variable as global, which means that the variable can be accessed and modified from anywhere in the program. This is useful when you want to create a variable that is accessible from multiple functions or modules. The global keyword is not present in TypeScript, as TypeScript does not have global variables. In Python, the global keyword is used to declare a variable as global, which can be useful when you want to create a variable that is accessible from multiple functions or modules.

def my_function():
    global x
    x = 5

In the above example, the global keyword is used to declare the variable x as global, which means that it can be accessed and modified from anywhere in the program. This can be useful when you want to create a variable that is accessible from multiple functions or modules. In TypeScript, you would not need to use the global keyword, as variables are scoped to the function or module in which they are defined.

nonlocal

The nonlocal keyword is used in Python to declare a variable as nonlocal, which means that the variable is not local to the current function but is defined in an outer function. This is useful when you want to access and modify a variable that is defined in an outer function. The nonlocal keyword is not present in TypeScript, as TypeScript does not have nested functions. In Python, the nonlocal keyword is used to declare a variable as nonlocal, which can be useful when you want to access and modify a variable that is defined in an outer function.

def outer_function():
    x = 5
    def inner_function():
        nonlocal x
        x = 10
    inner_function()
    print(x)

In the above example, the nonlocal keyword is used to declare the variable x as nonlocal, which means that it is not local to the inner_function but is defined in the outer_function. This allows the inner_function to access and modify the variable x defined in the outer_function. In TypeScript, you would not need to use the nonlocal keyword, as variables are scoped to the function in which they are defined. Nonlocal differs from declaring global in that nonlocal is used to access a variable in an outer function, while global is used to access a variable in the global scope. Hence nonlocal provides a level of access to variables that is between the local and global scope. In typescript, you would use a closure to access variables in an outer function.

function outerFunction() {
    let x = 5; // x is defined in the outer function thus it is accessible to the inner function, but not globally
    function innerFunction() {
        x = 10;
    }
    innerFunction();
    console.log(x);
}