Python

Python is a high-level, interpreted, strongly and dynamically typed general-purpose programming language. That just means it's built so humans can think more about what they want to do, and less about how the computer does it.

It was created by Guido van Rossum in 1991, not named after the snake, but after Monty Python's Flying Circus, a comedy show. That tells you something, Python doesn't take itself too seriously, but it can still get serious work done.

Python is dynamically typed, which means you don't have to say what type a variable is, it figures it out when you assign a value. You can even change that value to something completely different later on. It might sound chaotic, but in practice, it gives you flexibility and speed when writing code.

When you run Python code, it's not directly turned into machine language like C or assembly. Instead, it's first translated into something called bytecode, a kind of halfway language that's more compact and structured. That bytecode is then handed off to a special program called the Python Virtual Machine (PVM), which interprets and executes it line by line.

So, Python doesn't run your code directly. It compiles it into .pyc files (bytecode) and then the PVM interprets that and makes the magic happen at runtime.

It's like writing a play in English, having it translated into a special actor's script (bytecode), and then watching the actor (the PVM) perform it on stage, line by line.

Using an IDE

Let's say I create a file called myfile.py and write some serious code, like print("Hello, world") and run it using Visual Studio Code, commonly referred to as VSCode (an Integrated Development Environment (IDE) developed by Microsoft for Windows, Linux, macOS and web browsers).

You can think of VSCode as a fancy control room with buttons, lights and a Python technician standing by.

VSCode uses its built-in python extension to figure out which Python interpreter you're using (like /usr/bin/python3 or a virtual environment) and launch the interpreter, which is just a program named python or python3.

That interpreter reads myfile.py, line by line and compiles the source code (.py) into bytecode (a lower-level, efficient representation) and stores it in memory (or possibly in a .pyc file in __pycache__).

The Python Virtual Machine (PVM) takes that bytecode and executes it. The print() function sends your string to standard output and VSCode's terminal captures that output and displays Hello, world.

Using a Terminal

Running that code in a bash shell, is just the same but without a fancy IDE, just you and the machine.

# Using a REPL $ python3 >>> print("Hello, world") Hello, world >>> # Run a script file $ echo 'print("Hello, world")' > hello.py $ python3 hello.py Hello, world

A REPL or Read Eval Print Loop, is a simple interactive computer programming environment, that takes single user inputs, read the input, evalutate the input and returns the result to the user. You use the REPL to interact with the python interpreter.

You launch the python interpreter by typing python3 in your terminal and the OS (Linux, macOS or Windows) finds the python3 executable in your $PATH.

Python either:

  • Opens an interactive REPL session (if you didn't pass a file name).
  • Reads the content of the .py file, if you passed a file name.

The python interpreter parses and compiles the source code into bytecode and the PVM executes it line by line. In this case the print() function simply sends the output to stdout, which your terminal displays.

You can peek under the hood with Python's dis module, which is a great tool for peeking into Python's internals.

import dis dis.dis('print("Hello, world")') # You'll see something like this 0 RESUME 0 1 LOAD_NAME 0 (print) PUSH_NULL LOAD_CONST 0 ('Hello, world') CALL 1 RETURN_VALUE

The output we're seeing is the Python bytecode, the low-level instructions that the Python interpreter (CPython) executes. It's not machine code, but a simpler instruction set that the Python Virtual Machine can execute efficiently.

  • RESUME 0: This is a relatively new bytecode instruction (added in Python 3.11) that helps with exception handling and debugging. The 0 indicates this is at the start of the code (no prior instructions).
  • LOAD_NAME 0 (print): Looks up the name print (which is at index 0 in the names table) and pushes the print function object onto the stack
  • PUSH_NULL: Pushes a special NULL marker onto the stack (added in Python 3.11) which helps with method calls and error reporting.
  • LOAD_CONST 0 ('Hello, world'): Loads the constant string 'Hello, world' (index 0 in the constants table) and pushes it onto the stack.
  • CALL 1: Calls a callable (the print function) with 1 positional argument and takes the function and its arguments from the stack. The 1 indicates there's 1 positional argument.
  • RETURN_VALUE: Returns whatever is on top of the stack (the return value from print). In this case, since print() returns None, that would be returned.

Arithmetic Operators

It's important to gain practical experience in key AI concepts related to evaluating model performance and managing data storage, so you know how to compute and interpret model accuracy to assess how well your model is performing. This could involve basic statistics, such as mean and percentage calculations.

The usual order of mathematical operations also holds in Python, often referred to as PEMDAS:

  • Parentheses ( )
  • Exponents **
  • Multiplication/Division * /
  • Addition/Subtraction + -

A special operator in Python is the caret ^ and it's called bitwise XOR, which stands for exclusive OR. XOR (exclusive OR), means one or the other, but not both.

Computers don't really "know" numbers, they know bits, 0s and 1s, so when we do operations like addition, subtraction or XOR, we're really doing them bit by bit.

Let's imagine two light switches, A and B. Here's the rule:

A B A ^ B
0 0 0
0 1 1
1 0 1
1 1 0

It's like saying: "Give me a 1 if only one of the inputs is 1, if they're both 0 or both 1, give me a 0". Bitwise XOR can be used to swap two numbers without a temporary variable

x = 2 y = 3 x = x ^ y # 10 ^ 11 = 01 = 1 y = x ^ y # 01 ^ 11 = 10 = 2 x = x ^ y # 01 ^ 10 = 11 = 3 print(x, y) # Output: 3 2

It can also be used to find one odd number in a list where every other number appears twice.

nums = [2, 3, 2, 4, 3] result = 0 for num in nums: result ^= num print(result) # Output: 4

"I get how XOR works on bits... but why does XOR magically find the number that doesn't repeat?"

If we go through the loop step-by-step:

  • 0 ^ 2 = 2
  • 2 ^ 3 = 1
  • 1 ^ 2 = 3
  • 3 ^ 4 = 7
  • 7 ^ 3 = 4

Let's explore what XOR is really doing in the context of duplicates. The key insight is XOR is self-canceling.

# A number XOR'd with itself is always 0 a ^ a = 0 # A number XOR'd with 0 is itself a ^ 0 = a

So let's look at our list nums = [2, 3, 2, 4, 3].

Break it down:

  • 2 ^ 2 = 0 (these cancel out)
  • 3 ^ 3 = 0 (these cancel out)
  • Then you're left with 0 ^ 4 = 4

So all the duplicates vanish, and we're left with just the number that doesn't have a partner.

This might look confusing, but here's the trick: XOR is commutative and associative, so order doesn't matter.

2 ^ 3 ^ 2 ^ 4 ^ 3 # Which is the same as 2 ^ 2 ^ 3 ^ 3 ^ 4 # Which is the same as (2 ^ 2) ^ (3 ^ 3) ^ 4 # Which is the same as 0 ^ 0 ^ 4 = 4

So even if you're seeing 2, then 1, then 3, then 7 when we go step by step through the loop, those are just intermediate states. The magic happens at the end where all duplicates cancel out!