Skip to content

5.12 Lecture 10

Compiling a program (e.g.: C)

image

  1. Source Program (hello.c, text) : This is the original C code written by the programmer, stored as a text file (e.g., "hello.c").

  2. Pre-processor (cpp) : The preprocessor processes the source code, handling directives like #include​ and #define​. It expands macros and includes header files, producing a modified source program ("hello.i", still in text form).

  3. Compiler (cc1) : The compiler translates the modified source code ("hello.i") into assembly language code ("hello.s", also text). This step converts the high-level C code into low-level instructions specific to the target machine's architecture.

  4. Assembler (as) : The assembler converts the assembly code ("hello.s") into machine code, producing a relocatable object program ("hello.o", binary). This file contains machine instructions but isn't fully ready to run as it lacks resolved addresses for external references.

  5. Linker (ld) : The linker combines the relocatable object program ("hello.o") with necessary libraries (e.g., "stdlib.so") to resolve external references and produce a final executable object program ("hello", binary). This file is ready to be executed by the operating system.

Compiling Java

image

Compiling

  • It will recursively find any used class, if it’s not defined in the Java Standard Library, it will search in all directories given by the flag “-cp”, by default it is the current directory only. If the class is found (.class file) it will use it, if it finds the source file (.java file) it will compile it.
  • The compiler will do a static type checking.

Running Java

image

Running:

  • The Java Virtual Machine will recursively find any used class (as .class files), if it’s not defined in the Java Standard Library, it will search in all directories given by the flag “-cp”, by default it is the current directory only.
  • The Java Virtual Machine will check type dynamically.

Interpreting a Program

image

image

image

image

image

image

image

image

Static vs Dynamic Typingimage

C, Java is static typing language, since we need to define the type of each variable. And after that, we cannot change the type of that variable

But Python is dynamic typing language, we can do this

image

unsigned char is_prime(const unsigned int value) {
  int divisors = 1;
  for (int divisor = 2; divisor <= value; divisor++) {
    if (value % divisor == 0) {
      divisors++;
    }
  }
  return divisors == 2;
}
---
def is_prime(value):
  divisors = 0
  divisor = 1
  while divisor <= value:
    if value % divisor == 0:
      divisors += 1
  return divisors == 2

Documentation and type hints

def is_prime(value):
  divisors = 0
  divisor = 1
  while divisor <= value:
    if value % divisor == 0:
      divisors += 1
  return divisors == 2

---

def is_prime(value: int) -> bool:
    """
    Checks whether an integer value is a prime number or not
    :param value: the value to check
    :return: 'true' iff 'value' has only 2 divisors (1, and 'value')
    """
    divisors: int = 0
    divisor: int = 1
    while divisor <= value:
        if value % divisor == 0:
            divisors += 1
    return divisors == 2

Pass by value vs ?

In Java (as with C), arguments are passed by value, even if an argument is an object.

What’s the other option you can think of? Does Python uses “Pass by reference”? Yes and No, it uses “Pass by assignment"

def foo(a):
    print("Reference of 'a' at the start of foo: {}".format(id(a)))
    a += 1
    print("Reference of 'a' at the end of foo: {}".format(id(a)))


val_a = 0
print("Reference of 'val_a' before calling foo: {}".format(id(val_a)))
foo(val_a)
print("Reference of 'val_a' after calling foo: {}".format(id(val_a)))

The output is

The output is Reference of 'val_a' before calling foo:      139296701672360
Reference of 'a' at the start of foo:         139296701672360
Reference of 'a' at the end of foo:           139296701672392
Reference of 'val_a' after calling foo:       139296701672360

Since integers are immutable, Python creates a new integer object for the result (1​) with a different ID (139296701672392​). Now, a​ refers to this new object, but val_a​ still refers to the original 0​.


Python’s approach is neither purely one nor the other but often described as pass by assignment or pass by object reference which is often described as passing references to objects, but the behavior depends on the object’s mutability.

In Python, everything is an object, and variables are names bound to objects. When you pass an argument to a function, the function receives a reference to the same object (not a copy of the object).

However, what happens when you modify the parameter depends on whether the object is mutable (e.g., lists, dictionaries) or immutable (e.g., integers, strings, tuples):

  • Mutable objects: If you modify the object’s contents (e.g., append to a list), the changes are reflected outside the function because the reference points to the same object.
  • Immutable objects: If you try to modify the object (e.g., increment an integer), Python creates a new object, and the parameter now refers to this new object. The original object (and the caller’s variable) remains unchanged.

Example with a Mutable Object

To contrast, consider this modified code with a list:

def foo(a):
    print("Reference of 'a' at the start of foo: {}".format(id(a)))
    a.append(1)  # Modifies the list in place
    print("Reference of 'a' at the end of foo: {}".format(id(a)))

val_a = []
print("Reference of 'val_a' before calling foo: {}".format(id(val_a)))
foo(val_a)
print("Reference of 'val_a' after calling foo: {}".format(id(val_a)))
print("Value of 'val_a' after calling foo: {}".format(val_a))

Output:

Reference of 'val_a' before calling foo: 140735123456789
Reference of 'a' at the start of foo:    140735123456789
Reference of 'a' at the end of foo:      140735123456789
Reference of 'val_a' after calling foo:  140735123456789
Value of 'val_a' after calling foo:      [1]

Collections in Python

  • List is a collection which is ordered and changeable. Allows duplicate members.
  • Tuple is a collection which is ordered and unchangeable. Allows duplicate members.
  • Set is a collection which is unordered, unchangeable* and unindexed. No duplicate members.
  • Dictionary is a collection which is ordered** and changeable. No duplicate members.

  • *The items themselves are not changeable, the set is.

** Depending on the version of Python it can be ordered or unordered.

How to know that ?

a = "Hello World"
b = 10
c = 11.22
d = ("Geeks", "for", "Geeks")
e = ["Geeks", "for", "Geeks"]
f = {"Geeks": 1, "for":2, "Geeks":3}


print(type(a))
print(type(b))
print(type(c))
print(type(d))
print(type(e))
print(type(f))

"""
Output
<class 'str'>
<class 'int'>
<class 'float'>
<class 'tuple'>
<class 'list'>
<class 'dict'>
"""

Hello world in Python

def say_hi():
    """
    Prints a Hello World message
    :return: Nothing
    """
    print("Hello World")


say_hi()
print(say_hi.__doc__)

---
Output:
Hello World

    Prints a Hello World message
    :return: Nothing

Java vs Python functions

def function_name(arguments):
    BODY

Whenever possible, we will try to use type hints, only avoiding it when defining the hint itself can bring more complexity than readability benefits

def next_even(value: int) -> int:
    """
    Returns the next even number.
    :param value: the original value.
    :return: value + 1 if value is odd, value + 2 otherwise.
    """
    if value % 2 == 0:
        return value + 2
    return value + 1

Whenever possible, we will try to use type hints, only avoiding it when defining the hint itself can bring more complexity than readability benefits.

We will always define the function’s documentation

Java classes vs Python Modules

Whatever the language we use, organizing definitions is a common occurrence. This could be defining functions in a C file, a Java class, a Python Module, etc.

Remember we still want to keep these two properties (*):

  • Coupling: the degree of interdependence between modules. 👇
  • Cohesion: the degree of relatedness between elements within a module. 👆

*Here a module can refer to actual modules (classes in Java); but can also refer to functions.

Python Module

image

If a python file is named math.py, the module is called math.

And every definition d inside will be referred to as math.d

Example

module_a.py

def my_name_is():
    print("I'm module_a!")

print(__name__)

if __name__ == "__main__":
    my_name_is()

module_b.py

def my_name_is():
        print("I'm module_b!")

print(__name__)

if __name__ == "__main__":
        my_name_is()

modules.py

import module_a
import module_b

print(__name__)

module_a.my_name_is()
module_b.my_name_is()

module_a
module_b
main
I'm module_a!
I'm module_b!

📌 Additional Note: __name__​ and Module Behavior

Scenario Value of__name__ Willif __name__ == "__main__"​execute?
Module is run directly "__main__" ✅ Yes
Module is imported "module_name" ❌ No

Purpose:

Using if __name__ == "__main__"​ prevents certain code from running when the module is imported. This is considered best practice for writing reusable modules.

🧪 Why does importing a module execute some code?

  • When a module is import​ed, its top-level code is executed immediately once.
  • This includes:

  • Function definitions

  • Class definitions
  • Top-level statements like print()​, variable assignments, etc.
  • This does not include:

  • Code inside function or class bodies

  • Code under if __name__ == "__main__"​ (unless the module is run as the main program)

image


Using Python files as modules vs as programs

If you define a set of functions, constants, classes, etc in a Python file. But also defined a program-like behavior, you wouldn't want that part to be executed when you use that Python file as a module.

if __name__ == "__main__":
    # Your code goes here

Getting command-line arguments

import sys

if __name__ == "__main__":
    print("Arguments received {}".format(len(sys.argv)))
    for arg in sys.argv:
        print("Command-line argument: {}".format(arg))

Object Oriented Programming (an introduction)

  • Describe values and operations on those values, while hiding the inner implementation.
    Just like an ADT, but the value and the operations are packed together
  • A class will describe the data structure (fields, attributes, properties), and the operations (procedures, methods) that can be applied to that data.
    An object is an instance of a class with a particular state.

Python classes

class ClassName:
    def __init__(self):
        self.__my_field = 42

    def foo(self, x):
        self.__my_field = 42 + x

    def __str__(self):
        return str(self._my_field)
  • The instance is not implied as in Java (using "this").
  • It is the first argument.
  • It must be called self.

Here’s the Greeter​ class and a simple “demo” script that exercises it purely via print statements

# greeter.py

class Greeter:
    """
    Simple class that stores a name and can produce greeting messages.
    """
    def __init__(self, name: str):
        self.name = name

    def greet(self) -> str:
        """
        Return a greeting for the stored name.
        """
        return ("Hello, {}!".format(name))
# demo.py

from greeter import Greeter

def main():
    # Example 1: basic greeting
    alice = Greeter("Alice")
    print("Greeting for Alice:", alice.greet())

    # Example 2: change the name attribute and greet again
    bob = Greeter("Bob")
    print("Greeting for Bob:", bob.greet())
    bob.name = "Charlie"
    print("After renaming Bob to Charlie:", bob.greet())

    # Example 3: empty name
    nobody = Greeter("")
    print("Greeting for an empty name:", nobody.greet())

if __name__ == "__main__":
    main()

Expected output:

Greeting for Alice: Hello, Alice!
Greeting for Bob: Hello, Bob!
After renaming Bob to Charlie: Hello, Charlie!
Greeting for an empty name: Hello, !

Abstract Data Types

An Abstract Data Type is a description of values and operations on those values, without defining the technical details on how those values are represented and how the operations work on those representations

Abstract Classes

from abc import ABC, abstractmethod

class List(ABC):
    """
    An abstract list, a linearly ordered collection of elements.
    """

    @abstractmethod
    def append(self, elem) -> bool:
        """
        Appends an element to the end of the list
        :param elem: the element to append
        :return: 'True' iff the element was successfully appended
        """
        pass
  • There are no interfaces in Python, in fact, we can’t declare just the sign of a function/method, that’s why we need to use “pass” (it’s an instruction that does nothing)
  • I’m only showing one method.
  • See that we can’t define the type of the elements in the list.
from list import List

class _Node:
    def __init__(self, value=None, next_node=None):
        self.__value = value
        self.__next_node = next_node

    # more methods

class LinkedList(List):
    def __init__(self):
        self.__head = None
        self.__size: int = 0

    def size(self) -> int:
        return self.__size

    # rest of the methods
  • We can have more than one class per module.
  • The underscore in _Node​ means that this class should not be used outside of this module, but we can’t control that.