1 - Introduction to Programming

At its core, programming is the act of instructing a machine on how to perform a specific task. It’s like teaching your dog to fetch, but in this case, the dog is your computer, and the ball might be, let’s say, displaying a photo on your screen.

Now, you might think that programming is just writing lines of code. Programming is actually a broader process that not only includes writing code but also problem-solving, system design, and logical thinking.

In the universe of programming, there are high-level and low-level languages. A low-level language, like assembly, is closer to what the machine understands, while a high-level language, such as Python or JavaScript, is more human-friendly. Picture having a conversation: high-level languages are like chatting with a friend over coffee in New York, while low-level languages are like trying to communicate with someone speaking a very specific, localized dialect.

Additionally, some programming languages are compiled, while others are interpreted. If a language is compiled, it means it’s translated into a machine-understandable language before being executed. On the other hand, interpreted languages are translated in real-time, as they run.

A brief history of Programming

Programming isn’t a new concept. In fact, it’s been with us long before computers existed in the form we know today. Devices like the abacus and the astrolabe are early examples of tools we used for intricate calculations.

However, it was with the advent of mechanical machines, like Charles Babbage’s Analytical Engine, that the foundation for modern programming was laid. We’re talking about the 19th century!

Over time, landmark languages like Fortran and COBOL emerged. These languages paved the way for the technological revolutions that would follow. With the evolution of languages also came new paradigms: first Procedural, then Object-Oriented, and more recently, Functional.

Today, we’re in a modern era dominated by web, mobile, and cloud programming. Every swipe on your phone or online purchase has lines and lines of code working behind the scenes.

Programming today

Programming is the engine of our modern society. From apps for ordering food to advanced artificial intelligence systems aiding medical research, programming is everywhere.

Beyond simplifying our daily lives, programming has a profound societal impact. It has enabled progress in automation, data analysis, and entertainment. And what’s even more exhilarating is that we’re just scratching the surface. With upcoming prospects on artificial intelligence, quantum computing and the Internet of Things (IoT), who knows what marvels await us in the programming world?

1.1 - The Computer

In today’s digital age, where electronic gadgets seamlessly integrate into our daily lives, understanding the bedrock upon which these marvels stand becomes not just an academic interest but a societal necessity. As we embark on this enlightening voyage into the heart of computers, we aim to demystify the intricate dance between the physical and the abstract, between the tangible hardware and the intangible software.

To the uninitiated, a computer might seem like a mere box—perhaps sometimes sleek and shiny—but a box nonetheless. Yet, within this “box” lies a universe of complexity and coordination.

Hardware represents the physical components of a computer: the Central Processing Unit (CPU) which is often likened to the brain of the system, the Random Access Memory (RAM) acting as a temporary storage while tasks are underway, storage devices that retain data, and peripherals like keyboards, mice, and monitors1.

On the other side of this duality is software, a set of instructions that guides the hardware. There are various types of software, from system software like the operating system (OS), which coordinates the myriad hardware components, to application software that allows users to perform specific tasks, such as word processing or gaming2.

The role of the operating system is pivotal. It acts as a bridge, translating user commands into instructions that the hardware can execute. If the hardware were an orchestra, the OS would be its conductor, ensuring each instrument plays its part harmoniously.


The binary system: decoding the language of machines

Human civilizations have developed numerous numbering systems over the millennia, but computers, with their logical circuits, have settled on the binary system. But why binary? Simply put, at the most foundational level, a computer’s operation is based on switches (transistors) that can be either ‘on’ or ‘off’, corresponding naturally to the binary digits, or ‘bits’, 1 and 0 respectively3.

In this binary realm, a bit is the smallest data unit, representing a single binary value. A byte, comprising 8 bits, can represent 256 distinct values, ranging from \(00000000\) to \(11111111\)4. This binary encoding isn’t restricted to numbers; it extends to text, images, and virtually all forms of data. For instance, in ASCII encoding, the capital letter ‘A’ is represented as \(01000001\).

In a following post we’ll describe in more details the binary system and introduce another system used a lot in relations to computers, the hexadecimal.


Memory and Storage: the sanctuaries of data

The concepts of memory and storage are pivotal in understanding computer architecture. Though sometimes used interchangeably in colloquial parlance, their roles in a computer system are distinct.

Memory, particularly RAM, is volatile, meaning information stored is lost once the computer is turned off. RAM serves as the computer’s “workspace”, temporarily storing data and instructions during operations. There are various RAM types, with DRAM and SRAM being the most prevalent5.

Contrastingly, Read-Only Memory (ROM) is non-volatile, used predominantly to store firmware—software intrinsically linked to specific hardware, requiring infrequent alterations.

In terms of data storage, devices like hard drives, SSDs, and flash drives offer permanent data retention. These storage mechanisms are part of the memory hierarchy, which ranges from the swift but limited cache memory to the expansive but slower secondary storage6.



References


  1. Patterson, D. & Hennessy, J. (2014). Computer Organization and Design. Elsevier. ↩︎

  2. Silberschatz, A., Galvin, P. B., & Gagne, G. (2009). Operating System Concepts. John Wiley & Sons. ↩︎

  3. Tanenbaum, A. (2012). Structured Computer Organization. Prentice Hall. ↩︎

  4. Brookshear, J. G. (2011). Computer Science: An Overview. Addison-Wesley. ↩︎

  5. Jacob, B., Ng, S. W., & Wang, D. T. (2007). Memory Systems: Cache, DRAM, Disk. Morgan Kaufmann. ↩︎

  6. Siewiorek, D. P. & Swarz, R. S. (2017). Reliable Computer Systems: Design and Evaluation. A K Peters/CRC Press. ↩︎

1.2 - Numerical Systems

Every day, we’re surrounded by numbers. From the alarm clock’s digits waking us up in the morning to the price of our favorite morning coffee. But, have you ever stopped to ponder the essence of these numbers? In this article, we will dive deep into the captivating world of numbering systems, unraveling how one number can have myriad representations depending on the context.

The decimal system: the bedrock of our daily life

From a tender age, we’re taught to count using ten digits: 0 through 9. This system, known as the decimal system, underpins almost all our mathematical and financial activities, from basic arithmetic to calculating bank interests1. Its roots trace back to our anatomy: the ten fingers on our hands, making it the most intuitive and natural system for us. Yet, its true charm emanates from its positional nature.

To grasp this concept, let’s dissect the number 237:

  • The rightmost digit (7) stands for the units’ position. That is, \(7 \times 10^0\) (any number raised to the power of 0 is 1). Therefore, its value is simply 7.
  • The middle digit (3) represents the tens’ position, translating to \(3 \times 10^1 = 3 \times 10 = 30\).
  • The leftmost digit (2) is in the hundreds’ position, decoding to \(2 \times 10^2 = 2 \times 100 = 200\).

When these values are combined,

$$2 \times 10^2 + 3 \times 10^1 + 7 \times 10^0 = 200 + 30 + 7 = 237$$

The binary system: computers’ coded language

While the decimal system reigns supreme in our everyday lives, the machines we use daily, from our smartphones to computers, operate in a starkly different realm: the binary world. In this system, only two digits exist: 0 and 1. It might seem restrictive at first glance, but this system is the essence of digital electronics. Digital devices, with their billions of transistors, operate using these two states: on (1) and off (0)2.

Despite its apparent simplicity, the binary system can express any number or information that the decimal system can. For instance, the decimal number 5 is represented as 101 in binary.

Binary, with its ones and zeros, operates in a manner akin to the decimal system, but instead of powers of 10, it uses powers of 2.

Consider the binary number 1011:

  • The rightmost bit denotes \(1 \times 2^0 = 1\).
  • The subsequent bit stands for \(1 \times 2^1 = 2\).
  • Next up is \(0 \times 2^2 = 0\).
  • The leftmost bit in this number signifies \(1 \times 2^3 = 8\).

Thus, 1011 in binary translates to the following in the decimal system:

$$1011 = 1 \times 2^3 + 0 \times 2^2 + 1 \times 2^1 + 1 \times 2^0 = 8 + 0 + 2 + 1 = 11$$

Hexadecimal system: bridging humans and machines

While the binary system is perfect for machines, it can get a tad cumbersome for us, especially when dealing with lengthy binary numbers. Here is where the hexadecimal system, employing sixteen distinct digits: 0-9 and A-F, with A representing 10, B as 11, and so forth, up to F, which stands for 153 comes to help.

Hexadecimal proves invaluable as it offers a more compact way to represent binary numbers. Each hexadecimal digit corresponds precisely to four binary bits. For instance, think of the binary representation of the number 41279 and notice how the hexadecimal system achieves a more succinct representation:

$$41279 = 1010 0001 0011 1111 = A13F$$

But the hexadecimal system is more than just a compressed representation of binary numbers; it’s a positional numbering system like decimal or binary but based on 16 instead of 10 or 2. Let’s see how to derive the decimal representation of the example number (A13F):

  • The rightmost digit represents \(F \times 16^0 = 15 \times 16^0 = 15\).
  • The subsequent one stands for \(3 \times 16^1 = 48\).
  • The next digit denotes \(1 \times 16^2 = 256\).
  • The leftmost digit in this number signifies \(A \times 16^3 = 10 \times 16^3 = 40960\).

Therefore, A13F in hexadecimal translates to the following in the decimal system:

$$A13F = A \times 16^3 + 1 \times 16^2 + 3 \times 16^1 + F \times 16^0 = 10 \times 4096 + 1 \times 256 + 3 \times 16 + 15 \times 1 = 40960 + 256 + 48 + 15 = 41279$$

Conclusion

Numbering systems are like lenses through which we perceive and understand the world of mathematics and computing. Although the decimal system may be the linchpin of our daily existence, it’s crucial to appreciate and comprehend the binary and hexadecimal systems, especially in this digital age.

So, the next time you’re in front of your computer or using an app on your smartphone, remember that behind that user-friendly interface, a binary world is in full swing, with the hexadecimal system acting as a translator between that realm and us.



References


  1. Ifrah, G. (2000). The Universal History of Numbers. London: Harvill Press. ↩︎

  2. Tanenbaum, A. (2012). Structured Computer Organization. New Jersey: Prentice Hall. ↩︎

  3. Knuth, D. (2007). The Art of Computer Programming: Seminumerical Algorithms. California: Addison-Wesley. ↩︎

1.3 - Boolean Logic

In life, we often seek certainties. Will it rain tomorrow, true or false? Is a certain action right or wrong? This dichotomy, this division between two opposing states, lies at the very core of a fundamental branch of mathematics and computer science: Boolean logic.

Named in honour of George Boole, a 19th-century English mathematician, Boolean logic is a mathematical system that deals with operations resulting in one of two possible outcomes: true or false, typically represented as 1 and 0, respectively1. In his groundbreaking work, “An Investigation of the Laws of Thought,” Boole laid the foundations for this logic, introducing an algebraic system that could be employed to depict logical structures.

Boolean operations

Within Boolean logic, several fundamental operations allow for the manipulation and combination of these binary expressions:

  1. AND: This operation yields true (1) only if both inputs are true. For instance, if you have two switches, both need to be in the on position for a light to illuminate.

  2. OR: It returns true if at least one of the inputs is true. Using the switch analogy, as long as one of them is in the on position, the light will shine.

  3. NOT: This unary operation (accepting only one input) simply inverts the input value. Provide it with a 1, and you’ll get a 0, and vice versa.

  4. NAND (NOT AND): It’s the negation of AND. It only returns false if both inputs are true.

  5. NOR (NOT OR): The negation of OR. It yields true only if both inputs are false.

  6. XOR (Exclusive OR): It returns true if the inputs differ. If both are the same, it returns false.

  7. XNOR (Exclusive NOR): The inverse of XOR. It yields true if both inputs are the same.

Why is this logic important in computing and programming?

Modern computing, at its core, is all about bit manipulation (those 1s and 0s we’ve mentioned). Every operation a computer undertakes, from basic arithmetic to rendering intricate graphics, involves Boolean operations at some level2.

In programming, Boolean logic is used in control structures, such as conditional statements (if, else) and loops, allowing programs to make decisions based on specific conditions.

Truth Tables: mapping Boolean logic

A truth table graphically represents a Boolean operation. It lists every possible input combination and displays the operation’s result for each combination3.

For instance:

ABA AND BA OR BA XOR BA NOR BA NAND BNOT AA NXOR B
111100001
100110100
010110110
000001111

Concluding thoughts

Boolean logic is more than a set of abstract mathematical rules. It’s the foundational language of machines, the code underpinning the digital age in which we live. By understanding its principles, not only do we become more proficient in working with technology, but we also gain a deeper appreciation of the structures supporting our digital world.



References


  1. Boole, G. (1854). An Investigation of the Laws of Thought. London: Walton and Maberly. ↩︎

  2. Tanenbaum, A. (2012). Structured Computer Organization. New Jersey: Prentice Hall. ↩︎

  3. Minsky, M. (1967). Computation: Finite and Infinite Machines. New Jersey: Prentice-Hall. ↩︎

1.4 - Set Up your Development Environment

Venturing into the world of programming might seem like a Herculean task, especially when faced with the initial decision: Where to begin? This article will guide you through the essential steps to set up your programming environment, ensuring a solid foundation for your coding journey.

Choosing a programming language

Choosing a programming language is the first and perhaps the most crucial step in the learning process. Several factors to consider when selecting a language include:

  1. Purpose: What do you want to code for? If it’s web development, JavaScript or PHP might be good options. If you’re into data science, R or Python might be more appropriate.
  2. Community: A language with an active community can be vital for beginners. A vibrant community usually means more resources, tutorials, and solutions available online.
  3. Learning curve: Some languages are easier to pick up than others. It’s essential to pick one that matches your experience level and patience.
  4. Job opportunities: If you’re eyeing a career in programming, researching the job market demand for various languages can be insightful.

While there are many valuable and potent languages, for the purpose of this course, we’ve chosen Python. This language is renowned for its simplicity and readability, making it ideal for those just starting out. Moreover, Python boasts an active community and a wide range of applications, from web development to artificial intelligence1.

Installing Python

For Windows users:

  1. Download the installer:
  2. Run the installer:
    • Once the download is complete, locate and run the installer .exe file.
    • Make sure to check the box that says “Add Python to PATH” during installation. This step is crucial for making Python accessible from the Command Prompt.
    • Follow the installation prompts.
  3. Verify installation:
    • Open the Command Prompt and type:
      python --version
      
    • This should display the version of Python you just installed.

For Mac users:

  1. Download the installer:
  2. Run the installer:
    • Once the download is complete, locate and run the .pkg file.
    • Follow the installation prompts.
  3. Verify installation:
    • Open the Terminal and type:
      python3 --version
      
    • This should display the version of Python you just installed.

For Linux (Ubuntu/Debian) users:

  1. Update packages:
    sudo apt update
    
  2. Install Python:
    sudo apt install python3
    
  3. Verify installation:
    • After installation, you can check the version of Python installed by typing:
      python3 --version
      

Integrated Development Environments (IDEs)

An IDE is a tool that streamlines application development by combining commonly-used functionalities into a single software package: code editor, compiler, debugger, and more. Choosing the right IDE can make the programming process more fluid and efficient.

When evaluating IDEs, consider:

  1. Language compatibility: Not all IDEs are compatible with every programming language.
  2. Features: Some IDEs offer features like auto-completion, syntax highlighting, and debugging tools.
  3. Extensions and plugins: Being able to customize and extend your IDE through plugins can be extremely beneficial.
  4. Price: There are free and paid IDEs. Evaluate whether the additional features of a paid IDE justify its cost.

For this course, we’ve selected Visual Studio Code (VS Code). It’s a popular IDE that’s free and open-source. It’s known for its straightforward interface, a vast array of plugins, and its capability to handle multiple programming languages2. Its active community ensures regular updates and a plethora of learning resources.

Installing Visual Studio Code

For Windows users:

  1. Download the installer:
  2. Run the installer:
    • Once the download is complete, locate and run the installer .exe file.
    • Follow the installation prompts, including accepting the license agreement and choosing the installation location.
  3. Launch VS Code:
    • After installation, you can find VS Code in your Start menu.
    • Launch it, and you’re ready to start coding!

For Mac users:

  1. Download the installer:
  2. Install VS Code:
    • Once the download is complete, open the downloaded .zip file.
    • Drag the Visual Studio Code .app to the Applications folder, making it available in the Launchpad.
  3. Launch VS Code:
    • Use Spotlight search or navigate to your Applications folder to launch VS Code.

For Linux (Ubuntu/Debian) users:

  1. Update packages and install dependencies:
    sudo apt update
    sudo apt install software-properties-common apt-transport-https wget
    
  2. Download and install the key:
    wget -q https://packages.microsoft.com/keys/microsoft.asc -O- | sudo apt-key add -
    
  3. Add the VS Code repository:
    sudo add-apt-repository "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main"
    
  4. Install Visual Studio Code:
    sudo apt update
    sudo apt install code
    
  5. Launch VS Code:
    • You can start VS Code from the terminal by typing code or find it in your list of installed applications.

Write and execute your first program

Once you’ve set up your programming environment, it’s time to dive into coding.

Hello, World!

This is arguably the most iconic program for beginners. It’s simple, but it introduces you to the process of writing and executing code.

print("Hello, World!")

Running the Hello World program Running the Hello World program

Triangle area and perimeter calculation

This program is a tad more intricate. It doesn’t just print out a message; it also performs mathematical calculations.

# User input
side1 = float(input("Enter the length of the first side: "))
side2 = float(input("Enter the length of the second side: "))
side3 = float(input("Enter the length of the third side: "))

# Perimeter calculation
perimeter = side1 + side2 + side3

# Area calculation using Heron's formula
s = perimeter / 2
area = (s*(s-side1)*(s-side2)*(s-side3)) ** 0.5

print(f"The triangle's perimeter is: {perimeter}")
print(f"The triangle's area is: {area:.2f}")

Running the Triangle program Running the Triangle program


Conclusion

Setting up a programming environment might appear daunting at first, but with the right tools and resources, it becomes a manageable and rewarding task. We hope this article provided you with a solid foundation to kickstart your programming journey. Happy coding!



References


  1. Lutz, M. (2013). Learning Python. O’Reilly Media. ↩︎

  2. Microsoft. (2020). Visual Studio Code Documentation. Microsoft Docs. ↩︎

2 - Starting Concepts

2.1 - Variables and Data Types

Understanding how variables and data types work is essential to master any programming language. In this article we will review the basic concepts of variables, operators, data types and type conversions using the Python language. We will cover both theory and practical examples so you can apply these concepts in your own programs.

Variables

A variable is a container to store data in the computer’s memory. We can think of it as a box with a label. The label is the variable name and inside the box its value is stored.

To declare a variable in Python we just write the name and assign a value:

age = 28
price = 19.95
student = True

Variable names must start with letters or underscore, and can only contain letters, numbers and underscores. It is recommended to use meaningful names that represent the purpose of the variable.

In Python variables do not need to be declared with a particular type. The type is inferred automatically when assigning the value:

age = 28 # age is integer type
price = 19.95 # price is float type
single = True # single is boolean type

Once assigned, a variable can change its value at any time:

age = 30 # We change age to 30

Scope and lifetime

The scope of a variable refers to the parts of the code where it is available. Variables declared outside functions are global and available throughout the file. Variables inside a function are local and only visible within it.

The lifetime is the period during which the variable exists in memory. Local variables exist while the function executes, then they are destroyed. Global variables exist while the program is running.

Assignment

Assignment with the = operator allows changing or initializing a variable’s value:

number = 10
number = 20 # Now number is 20

There are also compound assignment operators like += and -= that combine an operation with assignment:

number += 5 # Adds 5 to number (number = number + 5)
number -= 2 # Subtracts 2 from number

Data types

Data types define what kind of value a variable can store. Python has several built-in types, including:

Numerical: To store integer, float, and complex numeric values:

integer = 10
float = 10.5
complex = 3 + 4j

Strings: To store text:

text = "Hello World"

Boolean: For True or False logical values:

true_variable = True
false_variable = False

Collections: To store multiple values like lists, tuples and dictionaries:

  • Lists: Mutable sequences of values:

    list = [1, 2, 3]
    
  • Tuples: Immutable sequences of values:

    tuple = (1, 2, 3)
    
  • Dictionaries: Key-value pair structures:

    dictionary = {"name":"John", "age": 20}
    

It is important to choose the data type that best represents the information we want to store.


Operators

Operators allow us to perform operations with values and variables in Python. Some common operators are:

  • Arithmetic: +, -, *, /, %, //, **

  • Comparison: ==, !=, >, <, >=, <=

  • Logical: and, or, not

  • Assignment: =, +=, -=, *=, /=

Let’s see concrete examples of expressions using these operators in Python:

# Arithmetic
5 + 4 # Addition, result 9
10 - 3 # Subtraction, result 7
4 * 5 # Multiplication, result 20

# Comparison
5 > 4 # Greater than, result True
7 < 10 # Less than, result True

# Logical
True and False # Result False
True or False # Result True
not True # Result False

# Assignment
number = 10
number += 5 # Adds 5 to number, equivalent to number = number + 5

Each type of operator works with specific data types. We must use them consistently according to our variable data types.


Type conversions

Sometimes we need to convert one data type to another to perform certain operations. In Python we can convert explicitly or implicitly:

Explicit: Using functions like int(), float(), str():

float = 13.5
integer = int(float) # converts 13.5 to 13

text = "100"
number = int(text) # converts "100" to 100

Implicit: Python automatically converts in some cases:

integer = 100
float = 3.5
result = integer + float # result is 103.5, integer converted to float

Some conversions can generate data loss or errors:

float = 13.5
integer = int(float)

print(integer) # 13, decimals are lost

To prevent this we must explicitly choose conversions that make sense for our data.



Conclusion

In this article we reviewed key concepts like variables, operators, data types and conversions in Python. Applying these concepts well will allow you to efficiently manipulate data in your programs. I recommend practising with your own examples to gain experience using these features. Good luck in your Python learning!

2.2 - Input and output operations

Input/output operations (I/O) allow a program to communicate and exchange data with the outside world. In this article we will see in detail input operations from the keyboard or a file, and output to the screen or a file.

Screen output

Python also provides functions to send program output to “standard output”, usually the screen or terminal1.

The print() function displays the value passed as a parameter:

name = "Eric"
print(name) # displays "Eric"

We can print multiple values separated by commas2:

print("Hello", name, "!") # displays "Hello Eric!"

We can also use literal values without assigning to variables3:

print("2 + 3 =", 2 + 3) # displays "2 + 3 = 5"

Output formatting

Python provides various ways to format output4:

f-Strings: Allow inserting variables into a string:

name = "Eric"
print(f"Hello {name}") # displays "Hello Eric"

%s: Inserts string text into a format string:

name = "Eric"
print("Hello %s" % name) # displays "Hello Eric"

%d: Inserts integer numbers:

value = 15
print("The value is %d" % value) # displays "The value is 15"

.format(): Inserts values into a format string:

name = "Eric"
print("Hello {}. Welcome".format(name))
# displays "Hello Eric. Welcome"

These formatting options allow us to interpolate variables and values into text strings to generate custom outputs. We can combine multiple values and formats in a single output string2.


Keyboard input

Python provides built-in functions to read data entered by the user at runtime. This is known as “standard input”4.

The input() function allows reading a value entered by the user and assigning it to a variable. For example:

name = input("Enter your name: ")

This displays the message “Enter your name: " and waits for the user to enter text and press Enter. That value is assigned to the name variable2.

The input() function always returns a string. If we want to ask for a number or other data type, we must convert it using int(), float(), etc1:

age = int(input("Enter your age: "))
pi = float(input("Enter the value of pi: "))

Reading multiple values

We can ask for and read multiple values on the same line separating them with commas3:

name, age = input("Enter name and age: ").split()

The split() method divides the input into parts and returns a list of strings. We then assign the list elements to separate variables.

We can also read multiple lines of input with a loop4:

names = [] # empty list

for x in range(3):
   name = input("Enter a name: ")
   names.append(name)

This code reads 3 names entered by the user and adds them to a list.


Output to a file

In addition to printing to the screen, we can write output to a file using the open() function1:

file = open("data.txt", "w")

This opens data.txt for writing (“w”) and returns a file object.

Then we use file.write() to write to that file3:

file.write("Hello World!")
file.write("This text goes to the file")

We must close the file with file.close() when finished4:

file.close()

We can also use with to open and automatically close:

with open("data.txt", "w") as file:
   file.write("Hello World!")
   # no need to close, it's automatic

Reading files

To read a file we use open() in “r” mode and iterate over the file object1:

with open("data.txt", "r") as file:
   for line in file:
      print(line) # prints each line of the file

This prints each line, including newlines.

We can read all lines to a list with readlines()3:

lines = file.readlines()
print(lines)

To read the full content to a string we use read()4:

text = file.read()
print(text)

We can also read a specific number of bytes or characters with read(n)2.


File handling operations

There are several built-in functions to handle files in Python1:

  • open() - Opens a file and returns a file object
  • close() - Closes the file
  • write() - Writes data to the file
  • read() - Reads data from the file
  • readline() - Reads a line from the file
  • truncate() - Empties the file
  • seek() - Changes the reading/writing position
  • rename() - Renames the file
  • remove() - Deletes the file

These allow us to perform advanced operations to read, write and maintain files.


Conclusion

In this article we explained Python input and output operations in detail, including reading from standard input and writing to standard output or files. Properly handling input and output is essential for many Python applications. I recommend practising with your own examples to master these functions3.



References


  1. McKinney, W. (2018). Python for data analysis: Data wrangling with Pandas, NumPy, and IPython. O’Reilly Media. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. Lutz, M. (2013). Learning Python: Powerful Object-Oriented Programming. O’Reilly Media, Incorporated. ↩︎ ↩︎ ↩︎ ↩︎

  3. Matthes, E. (2015). Python crash course: A hands-on, project-based introduction to programming. No Starch Press. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  4. Downey, A. B. (2015). Think Python: How to think like a computer scientist. Needham, Massachusetts: Green Tea Press. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

2.3 - Flow Control

When we embark on the exciting journey of learning to program, we soon discover that programming is not just about writing code, but also about controlling the flow of that code. We can compare it to the flow of decisions we make in our daily lives. For example, if it’s cold outside, we put on a coat before going out. If we have no pending tasks, we go to the movies. Our actions depend on these evaluations and decisions. Control flow is, essentially, the way we decide which part of the code runs, when it runs, and how many times it does. To do this, we have a variety of structures that allow us to make decisions, repeat actions, and split our code into logical blocks.

Conditions: making decisions in code

Life is full of decisions: “If it rains, I’ll take an umbrella. Otherwise, I’ll wear sunglasses.” These decisions are also present in the world of programming. Conditions are like questions the computer asks itself. They allow us to make decisions and execute specific code based on a condition1. They can be as simple as “Is it raining?” or as complex as “Is it the weekend and do I have less than $100 in my bank account?”.

if

The if structure allows us to evaluate conditions and make decisions based on the result of that evaluation.

age = 15

if age >= 18:
    print("You are an adult")

The code above allows executing a portion of code if a person’s age is greater than or equal to 18 years.

if-else

When you want to execute alternative code if the condition is false, you use the if-else structure.

age = 21
if age >= 18:
    print("You are an adult")
else:
    print("You are a minor")

In this case, it determines if the person is an adult or a minor, and the message displayed is different.

if-elif-else

When conditions are multiple and two paths are not enough, the if-elif-else structure is used to evaluate them in a chained way.

age = 5
if age <= 13:
    print("You are a child")
elif age > 13 and age < 18:
    print("You are a teenager")
else:
    print("You are an adult")

In the code above, there are three clear paths: one for when age is less than or equal to 13, one for when age is between 13 and 18, and another for when age is greater than or equal to 18.

Another way to solve this problem is through the switch-case structure, which, although Python does not natively incorporate, other languages like Java or C++ do, and it is an important tool to be familiar with. This structure allows programmers to handle multiple conditions in a more organized way than a series of if-elif-else.

In Java, for example:

int day = 3;
switch(day) {
  case 1:
    System.out.println("Monday");
    break;
  case 2:
    System.out.println("Tuesday");
    break;
  case 3:
    System.out.println("Wednesday");
    break;
  // ... and so on
  default:
    System.out.println("Invalid day");
}

In the previous example, depending on the value of day, the corresponding day will be printed2.


Loops: repeating actions

Sometimes in programming we need to repeat an action several times. Instead of writing the same code many times, we can use loops. These allow repeating the execution of a block of code while a condition is met3.

while

The while loop is useful when we want to repeat an action based on a condition.

# Prints 1 to 5
i = 1
while i <= 5:
    print(i)
    i = i + 1

do-while

Similar to while but guarantees at least one execution since the code block is executed first and then the condition is evaluated. Python does not implement this structure, but other languages like Java and C++ do.

int i = 1;

do {
  System.out.println(i);
  i++;
} while(i <= 5);
int number = 0;
do {
  std::cout << "Hello, world!" << std::endl;
  number++;
} while (number < 5);

for

The for loop is useful when we know how many times we want to repeat an action.

for i in range(5):
    print("Hello, world!")

The code above will print “Hello, world!” five times.

We can also iterate over the elements of a list or iterable object:

names = ["Maria", "Florencia", "Julian"]
for name in names:
    print(f"Hello {name}")

# Prints
# Hello Maria
# Hello Florencia
# Hello Julian

The break and continue statements

We can use break to terminate the loop and continue to skip to the next iteration.

break is used to completely terminate the loop when a condition is met, in the following example, when i reaches 5.

# break example
i = 0
while i < 10:
  print(i)
  if i == 5:
    break
  i += 1

# Prints:
# 0
# 1
# 2
# 3
# 4
# 5

continue is used to skip an iteration of the loop and continue with the next one when a condition is met. Here we use it to skip even numbers.

# continue example
i = 0
while i < 10:
  i += 1
  if i % 2 == 0:
    continue
  print(i)

# Prints:
# 1
# 3
# 5
# 7
# 9

Nesting: combining structures

Control flow structures can be nested within each other. For example, we can have loops within loops or conditions within loops.

for i in range(5):
  for j in range(10):
    if (i % 2 == 0 and j % 3 == 0):
      print(f"i = {i}, j = {j}")

This code will print combinations of i and j only when i is divisible by 2 and j is divisible by 3, demonstrating how loops are nested and executed3.


Common usage patterns

There are specific patterns to solve common needs with control flow.

Search for a value in a collection:

fruits = ["apple", "orange"]

searching = "orange"
found = False

for fruit in fruits:
    if fruit == searching:
        found = True
        break

if found:
    print("Fruit found!")

Accumulation

Accumulate incremental values in a loop:

total = 0

for i in range(10):
    total += i

print(total) # Sum from 0..9 = 45

Flowcharts: the visual route to understanding code

Programmers, whether beginners or experts, often find themselves facing challenges that require detailed planning before diving into code. This is where flowcharts come into play as an essential tool. These charts are graphical representations of the processes and logic behind a program or system. In this article, we will unravel the world of flowcharts, from basic concepts to advanced techniques, and how they can benefit programmers of all levels.

A flowchart is a graphical representation of a process. It uses specific symbols to represent different types of instructions or actions. Its main purpose is to simplify understanding of a process by showing step by step how information or decisions flow. These charts:

  • Facilitate understanding of complex processes.
  • Aid in the design and planning phase of a program.
  • Serve as documentation and reference for future developments.

Flowcharts are a powerful tool that not only benefits beginners but also experienced programmers. They provide a clear and structured view of a process or program, facilitating planning, design, and communication between team members.

Basic elements

Flowcharts consist of several symbols, each with a specific meaning:

  • Oval: Represents the start or end of a process.
  • Rectangle: Denotes an operation or instruction.
  • Diamond: Indicates a decision based on a condition.
  • Arrows: Show the direction of flow.
graph TD;
    start((Start))
    process[Process]
    decision{Decision?}
    final((End))

    start --> process;
    process --> decision;
    decision --> |Yes| process
    decision --> |No| final

Examples

Let’s design a flowchart for a program that asks for a number and tells us if it’s even or odd.

graph TB
    start((Start))
    input[Input number]
    decision{Even?}
    isEven[Is even]
    isOdd[Is odd]
    final((End))

    start --> input
    input --> decision
    decision --> |Yes| isEven
    decision --> |No| isOdd
    isEven --> final
    isOdd --> final

As programs become more complex, you may need to incorporate loops, multiple conditions, and other advanced elements into your flowchart. For example, here we diagram a program that sums numbers from 1 to a number entered by the user.

graph TD
    start((Start))
    input[Input number]
    setVariables[Set sum=0 and counter=1]
    loop_condition{counter <= N?}
    loop_code[Add value and increment counter]
    result[Show sum]
    final((End))

    start --> input
    input --> setVariables
    setVariables --> loop_condition
    loop_condition --> |Yes| loop_code
    loop_code --> loop_condition
    loop_condition --> |No| result
    result --> final

Conclusion

Control flow is the heart of programming. Without it, programs would be linear sequences of actions without the ability to make decisions or repeat tasks. By mastering these structures not only do you improve your ability to write code, but also your ability to think logically and solve complex problems.



References


  1. Lutz, M. (2013). Learning Python: Powerful Object-Oriented Programming. O’Reilly Media, Incorporated. ↩︎

  2. Deitel, P., & Deitel, H. (2012). Java: How to program. Upper Saddle River, NJ: Prentice Hall. ↩︎

  3. Matthes, E. (2015). Python crash course: A hands-on, project-based introduction to programming. San Francisco, CA: No Starch Press. ↩︎ ↩︎

2.4 - Functions

In the vast and exciting world of programming, there are concepts that are fundamental pillars for any developer, regardless of their experience level. One of these concepts is functions. What are they? Why are they so crucial? Let’s find out!

What are functions?

A function, in simple terms, is a block of code that executes only when called. You can think of it as a small program within your main program, designed to perform a specific task1. A function can also be seen as a black box: we pass an input (parameters), some internal processing occurs, and it produces an output (return value).

Functions allow us to segment our code into logical parts where each part performs a single action. This provides several benefits2:

  • Reusability: Once defined, we can execute (call) that code from anywhere in our program as many times as needed.
  • Organization: It allows dividing a large program into smaller, more manageable parts.
  • Encapsulation: Functions reduce complexity by hiding internal implementation details.
  • Maintainability: If we need to make changes, we only have to modify the code in one place (the function) instead of tracking down all instances of that code.

Procedures vs. Functions

It is vital to distinguish between these two concepts. While a function always returns a value, a procedure performs a task but does not return anything. In some languages, this difference is clearer than in others. Python, for example, has functions that can optionally return values.


Anatomy of a function

In Python, a function is declared using the def keyword, followed by the function name and parentheses. The code inside the function is called the body of the function3 and contains the set of instructions to execute to perform its task.

def my_function():
    print("Hello from my function!")

To call or invoke a function, we simply use its name followed by parentheses:

my_function() # Output: Hello from my function!

Parameters and arguments

Functions become even more powerful when we pass information to them, known as parameters. These act as “variables” inside the function, allowing the function to work with different data each time it is called.

While parameters are variables defined in the function definition, arguments are the actual values passed when calling the function.

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

greet("Maria")
# Output:
#   Hello Maria!

Python allows default parameters, which have a predetermined value, making passing those arguments optional when calling the function. It also allows named parameters which enable passing arguments in any order by specifying their name.

def greet(name="Maria", repetitions=3):
    repetition = 1
    while repetition <= repetitions:
        print(f"Hello {name}!")
        repetition += 1

greet()
# Output:
#   Hello Maria!
#   Hello Maria!
#   Hello Maria!

greet("Florencia", 4)
# Output:
#   Hello Florencia!
#   Hello Florencia!
#   Hello Florencia!
#   Hello Florencia!

greet(repetitions=2, name="Julian")
# Output
#   Hello Julian!
#   Hello Julian!

Returning values

Functions can return a result or return value using the return keyword.

def circle_area(radius):
    return 3.14 * (radius ** 2)

result = circle_area(10)
print(result) # Output: 314

The return value is passed back to where the function was called and can be assigned to a variable for later use.

Functions can also perform some task without explicitly returning anything. In Python this is known as returning None.


Local and global variables

Local variables are defined inside a function and only exist in that scope, while global variables are defined outside and can be accessed from anywhere in the code. It is crucial to understand their scope (where a variable is accessible) and lifetime (how long a variable lives).

x = 10 # x is global

def add():
    y = 5 # y is local
    return x + y

add() # Output: 15
print(y) # Error, y does not exist outside the function

We can read global variables from a function, but if we need to modify it we must declare it global.

x = 10

def add():
    global x
    x = x + 5

add()
print(x) # 15

Best Practices

When creating functions we should follow certain principles and patterns4:

  • The name of a function should clearly indicate its purpose.
  • Make functions small, simple, and focused on one task. A function should do one thing and do it well.
  • Use descriptive names for functions and parameters.
  • Avoid side effects and modifying global variables.
  • Properly document the purpose and usage of each function.
  • Limit the number of parameters, ideally 0 to 3 parameters.

Following these best practices will help us create reusable, encapsulated, and maintainable functions.


Conclusion

Functions are core components in programming, allowing us to organize, reuse, and encapsulate code. By defining functions that perform a single task we keep our programs simplified, easy to understand, and modify. By understanding and mastering this concept, you not only improve the quality of your code but also your efficiency as a developer.



References


  1. McConnell, S. (2004). Code Complete. Microsoft Press. ↩︎

  2. Joyanes Aguilar, L. (2008). Fundamentos de programación: algoritmos, estructura de datos y objetos. McGraw-Hill. ↩︎

  3. Python Software Foundation. (2022). Python Official Documentation↩︎

  4. Kindler, E., & Krivy, I. (2011). Object-Oriented Simulation of systems with Java: A working introduction. BoD–Books on Demand. ↩︎

2.5 - Recursive Functions

Recursion is a fundamental concept in programming that allows a function to call itself. At first it may seem counterintuitive, but mastering this approach opens the door to elegant solutions for certain problems.

Recursion: the art of calling yourself

Imagine a box of mirrors where each mirror reflects what it sees in the next, creating an infinite series of reflections. Recursion in programming is something similar. It is a technique where a function calls itself directly or indirectly1. This creates a cycle where the function solves a problem by dividing it into smaller instances of the same problem, until reaching a simple base case that can be solved directly.

For example, let’s imagine a function that prints a countdown:

def countdown(number):

    if number > 0:
        print(number)
        countdown(number - 1)
    else:
        print("Blastoff!")

countdown(5)

This function calls itself recursively decrementing the number each time until reaching 0, and then prints the blastoff message.

Recursion is a declarative approach that focuses on dividing a problem into recursive cases without needing to explicitly control the loop using iterators or counters like in imperative programming.


The structure of a recursive function

The power of recursion lies in its simplicity. However, it is essential to understand its structure to avoid common pitfalls. A typical recursive function has two main parts2:

  1. Base case: The simplest case with a known solution that doesn’t require recursion. It is the stopping condition that halts the recursion. Without a base case, we would fall into infinite recursion which eventually overflows the call stack.
  2. Recursive case: This is where the magical recursive call occurs. At this point, the function calls itself with a modified argument, usually a reduced version of the original problem.

Classic recursion examples

Factorial

The factorial of a positive integer \(n\) is the product of all positive integers less than or equal to \(n\). For example:

  • \(5! = 5 * 4 * 3 * 2 * 1 = 120\)
  • \(4! = 4 * 3 * 2 * 1 = 24\)
  • \(3! = 3 * 2 * 1 = 6\)

Here is the Python code for calculating factorial using recursion:

def factorial(n):
    if n == 1:
        return 1       # Base case
    return n * factorial(n-1) # Recursive case
  • Base case: The base case is the simplest, smallest instance of the problem that can be answered directly. For factorial, when \(n = 1\), the result is \(1\).
  • Recursive case: If \(n\) is greater than \(1\), the function calls itself with \(n-1\), and multiplies the result by \(n\).

Let’s say you want to calculate the factorial of \(5\), so you call factorial(5).

Here is what happens:

  1. Step 1: Since \(n = 5\) is not \(1\), the function calls factorial(4), then multiplies the result by \(5\).
  2. Step 2: Now, inside factorial(4), \(n = 4\), so the function calls factorial(3), then multiplies the result by \(4\).
  3. Step 3: Inside factorial(3), \(n = 3\), so it calls factorial(2), then multiplies the result by \(3\).
  4. Step 4: Inside factorial(2), \(n = 2\), so it calls factorial(1), then multiplies the result by \(2\).
  5. Step 5: Finally, factorial(1) reaches the base case, where \(n = 1\), so it returns \(1\).

Now the results unwind:

  • factorial(2) returns \(2 * 1 = 2\)
  • factorial(3) returns \(3 * 2 = 6\)
  • factorial(4) returns \(4 * 6 = 24\)
  • factorial(5) returns \(5 * 24 = 120\)

The final result is \(120\), which is the value of \(5!\).

Here is a visual representation of the call stack:

factorial(5)
  -> factorial(4)
    -> factorial(3)
      -> factorial(2)
        -> factorial(1)
          return 1
        return 2
      return 6
    return 24
  return 120

Fibonacci sequence

The Fibonacci sequence is a series of numbers where each number is the sum of the previous two. It starts with \(0\) and \(1\), and each subsequent number is the sum of the two numbers before it. The beginning of the sequence is: \(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …\)

Here is the Python code for calculating the \(n^th\) Fibonacci number using tail recursion:

def fibonacci(n, a=0, b=1):
    if n == 0:
        return a
    return fibonacci(n-1, b, a+b)

The function takes three parameters:

  • \(n\): The position of the desired number in the sequence.
  • \(a\) and \(b\): Two numbers that aid in the sequence calculation.

Here is a breakdown of how the function works:

  1. Base case: If \(n\) is \(0\), the function returns \(a\). This is the value of the \(n^th\) number in the sequence.
  2. Recursive case: If \(n\) is not \(0\), the function calls itself with \(n-1\), \(b\), and \(a+b\). These parameters change the position in the sequence and prepare the next numbers for summation.

Suppose we want to find the \(5^th\) number in the Fibonacci sequence by calling fibonacci(5).

Here is what happens:

  1. Step 1: Since \(n = 5\), it calls fibonacci(4, 1, 1) (because \(a = 0\), \(b = 1\), \(a + b = 1\)).
  2. Step 2: Since \(n = 4\), it calls fibonacci(3, 1, 2) (because \(a = 1\), \(b = 1\), \(a + b = 2\)).
  3. Step 3: Since \(n = 3\), it calls fibonacci(2, 2, 3) (because \(a = 1\), \(b = 2\), \(a + b = 3\)).
  4. Step 4: Since \(n = 2\), it calls fibonacci(1, 3, 5) (because \(a = 2\), \(b = 3\), \(a + b = 5\)).
  5. Step 5: Since \(n = 1\), it calls fibonacci(0, 5, 8) (because \(a = 3\), \(b = 5\), \(a + b = 8\)).
  6. Step 6: Since \(n = 0\), it returns \(a\), which is \(5\).

The result is \(5\), which is the \(5^th\) number in the Fibonacci sequence.

Here is a visual representation of the call stack:

fibonacci(5, 0, 1)
  -> fibonacci(4, 1, 1)
    -> fibonacci(3, 1, 2)
      -> fibonacci(2, 2, 3)
        -> fibonacci(1, 3, 5)
          -> fibonacci(0, 5, 8)
            return 5

Advantages and Disadvantages

Recursion has certain advantages3:

  • It can result in simple, elegant solutions for problems that easily break down into subproblems.
  • It eliminates the need for explicit loop control.
  • It mirrors the mathematical structure of a recursive definition.

The disadvantages include:

  • It can be less efficient (high memory consumption) than iteration due to repeated function calls and stack frame creation.
  • Too much recursion can overflow the call stack and cause crashes.
  • It can be harder to debug and analyze than iteration.

Therefore, recursion is a powerful tool that should be used judiciously in appropriate cases.


Recursion vs Iteration

Recursion and iteration (using loops) are parallel tools and we can use either one to solve many problems. Both techniques have the potential to solve the same problems, but their implementation and efficiency may vary. Let’s take the factorial example:

Iterative

def factorial_iterative(n):
    result = 1
    for i in range(1, n+1):
        result *= i
    return result

Recursive

def factorial_recursive(n):
    if n == 1:
        return 1
    return n * factorial(n-1)

The iterative version is more efficient in terms of space, but the recursive is cleaner and easier to understand. The choice between recursion and iteration often depends on the specific problem, memory constraints, and programmer preferences.


Conclusion

Recursion is a key technique that allows writing elegant, natural, and efficient algorithms when properly leveraged. Understanding how to break down a problem into recursive cases is essential to master this skill. Recursion provides a unique declarative alternative to solve complex problems without managing explicit loops. However, it is crucial to always remember to define an adequate base case and be aware of recursion limitations in terms of efficiency and memory usage. Knowing how to combine recursion and iteration gives flexibility when creating optimal solutions.

As always, the key lies in finding the right balance and using the right tool for the right job.



References


  1. Cormen, T.H., Leiserson, C.E., Rivest, R.L., & Stein, C. (2009). Introduction to Algorithms. MIT Press. ↩︎

  2. Kindler, E., & Krivy, I. (2011). Object-Oriented Simulation of systems with Java: A working introduction. BoD–Books on Demand. ↩︎

  3. Lutz, M. (2013). Learning Python: Powerful Object-Oriented Programming. O’Reilly Media, Incorporated. ↩︎

3 - Object-Oriented Programming

Object-Oriented Programming (OOP) is a programming paradigm that has become indispensable nowadays. This approach models real-world elements as “objects” that have properties and behaviours, which allows for more intuitive and maintainable programmes to be created. In this article we will look at the basic concepts of OOP and its advantages over other paradigms like procedural programming. Let’s get started!

This paradigm is based on two fundamental concepts:

  • Objects: entities that combine state (data) and behaviour (operations) in a single unit. For example, a “car” object would have properties like colour, number of doors, maximum speed, etc. And behaviours like accelerate, brake, steer, etc.
  • Classes: specifications that define the common structure and behaviour of a group of objects. The “car” class would serve as a template for creating car objects with the same characteristics.

As explained by programmer Alan Kay, one of the creators of OOP:

“The big idea is to design programmes in terms of conceptual objects and concepts from the real world. The interfaces with the real world should, therefore, be constructed in terms of these conceptual objects.”1

That is, OOP conceptually models real-world elements to make programming more intuitive.


Programming paradigms

Before delving into OOP, it is worth understanding that there are different paradigms or approaches to tackle programming. The main ones are:

Procedural programming

Ordered sequence of instructions that the programme must follow step-by-step. The focus is on procedures and functions. For example, C is a language geared towards procedural programming.

Procedural programming is better for:

  • Simple problems or sequential algorithms.
  • Code that won’t need heavy reusing or expanding.
  • Cases where performance and efficiency are critical.

Object-Oriented programming

Model based on objects that contain data and code in cohesive units. The focus is on classes and the interaction between objects. For example, Java and Python are object-oriented languages.

OOP allows modelling real-world elements more directly, better encapsulating data, and reusing code through inheritance between classes.

The main advantages of OOP over procedural programming are:

  • Modularity: objects group related data and operations, encapsulating internal complexity. This allows working with independent modules.
  • Information hiding: Objects can expose a simple interface and hide internal implementation details. This reduces coupling.
  • Reusability: Classes enable code reuse. An abstract class can inherit to multiple subclasses.
  • Extensibility: We can extend the behaviour of parent classes by creating new subclasses.
  • Conceptual mapping: Objects represent real-world entities, which eases the translation of requirements into code.

However, OOP also has disadvantages. According to programmer Paul Graham:

“Object-oriented programming often makes things harder than they need to be.”2

For example, for simple problems OOP can be excessive. And in large projects there is a risk of overusing inheritance and polymorphism, making the code difficult to follow.

Ultimately, OOP is more suitable when:

  • The problem to be modelled has clear, structured entities.
  • We want to reuse encapsulated code in modular classes.
  • We work on systems that need to be easily extended and maintained.

Basic OOP concepts

Now that we know the general ideas behind OOP, let’s look at some key concepts:

Objects

An object is a combination of data (properties) and behaviours (methods). For example, a Car object would have properties like make, model, colour and methods like accelerate, brake, etc.

# Car Class
class Car:

  def __init__(self, make, colour):
    self.make = make
    self.colour = colour

  def accelerate(self):
    print("Accelerating", self.make)

# Create Object
my_car = Car("Toyota", "Red")
  • my_car is now a Car object with properties and methods defined in its class.

Classes

A class defines the common attributes (properties) and methods (functions) for a group of objects. It works like a template for creating similar objects.

By convention, classes are defined with the first letter capitalised. The properties and methods of a class get the self prefix to indicate they belong to that object instance.

class Circle:

  def __init__(self, radius):
    self.radius = radius

  def area(self):
    return 3.1416 * (self.radius ** 2)
  • Circle defines the class with radius property and area() method.

Methods

Methods are functions that define the behaviour of an object. They are declared inside the class and can access the object’s properties via self.

A constructor is a special method (__init__) that runs when creating objects to initialise their properties.

class Person:

  def __init__(self, name, age):
    self.name = name
    self.age = age

  def greet(self):
    print(f"Hello! I'm {self.name}")

john = Person("John", 30) # Runs __init__
john.greet() # "Hello! I'm John"
  • The constructor assigns name and age. The greet() method accesses the name attribute.

Properties

Properties are variables associated with an object that define its characteristics or state. They are declared in the class and accessed through the object reference.

class Rectangle:

  def __init__(self, height, width):
    self.height = height
    self.width = width

r = Rectangle(3, 4)
print(r.height) # 3
r.width = 8
print(r.width) # 8

It is recommended to declare properties as private and access via getter/setter methods to respect encapsulation.

Encapsulation

It consists of hiding the internal implementation details of an object, exposing only a public interface. This is achieved by declaring methods and properties with public or private modifiers.

In Python this is denoted with a leading underscore for private methods/properties:

class BankAccount:

  def __init__(self, balance=0):
    self.balance = balance

  def deposit(self, amount):
    self._balance += amount

  def check_balance(self):
    return self._balance
  • _balance is private, only accessed internally or via check_balance().

Encapsulation makes it easier to change internal parts of a class without affecting its public interface.


Conclusion

Object-Oriented Programming models real-world elements as classes and objects, prioritising modularity, information hiding, and code reuse to create more robust and maintainable programmes.

Although it can be excessive for simple problems, OOP is ideal for medium/large systems that need to expand and evolve in complexity over time.

Concepts like inheritance, polymorphism, abstraction, and interfaces allow fully leveraging the advantages of this paradigm. With a solid understanding of its fundamentals we are ready to apply OOP in any programming language and project.

References


  1. Kay, Alan. The early history of Smalltalk. http://gagne.homedns.org/~tgagne/contrib/EarlyHistoryST.html ↩︎

  2. Graham, Paul. Why Arc Isn’t Especially Object-Oriented. http://www.paulgraham.com/noop.html ↩︎

3.1 - Classes and objects

In object-oriented programming, classes and objects are the key concepts to understand how we model elements of reality and define their structure and behaviour within software. Let’s look in detail at the anatomy of a class, how to create objects from it to use their properties and methods, and other key details of their relationship.

Anatomy of a class

A class acts as a blueprint or mould to construct similar objects, defining their common characteristics and functionalities. It is similar to the blueprint used to construct houses in the same neighbourhood: they all share certain key attributes.

The typical components of a class are:

Attributes (properties): Variables that characterise the object. For example, for a Person class, attributes like name, age, ID, etc.

class Person:
  id = ""
  name = ""
  age = 0

Methods: Functions that define behaviours. For example, a Person can walk(), talk(), eat(), etc. They access the attributes to implement said functionality.

Constructor: Special __init__() method that executes when instantiating the class and allows initialising attributes.

Destructor: __del__() method that executes when deleting the instance, freeing up resources. Optional in some languages.


Creating objects

From the class we generate objects, which are specific instances with their own defined attributes. Let’s say the House class is the blueprint, and a specific house on a particular street is the object.

In code, we create an object by invoking the class as if it were a method:

# Person class
class Person:
  def __init__(self, n, a):
    self.name = n
    self.age = a

# Specific Person objects
john = Person("John", 30)
mary = Person("Mary", 35)

Each object shares the general structure and behaviour but can store different data.

Using properties and methods

We now have a Person class and a john object of type Person. How do we interact with the object?

  • Properties: It is possible to access the value of an object attribute using the object reference (john) and the attribute name.
john.name  # "John"
john.age   # 30
  • Methods: In the same way as accessing attributes but adding parentheses inside which arguments are passed if it takes any.
# Person class
class Person:
  def __init__(self, n, a):
      self.name = n
      self.age = a

  def eat(self, food):
      print(f"Eating {food}")

# Specific Person object
john = Person("John", 30)
john.eat("pizza") # Prints "Eating pizza"

The john object now has its own state (properties) and behaviour (methods).


Self vs This

An important detail in methods is how they access the object’s attributes and other methods. Here another difference between languages comes into play:

  • Self: In Python, attributes and methods are accessed within the class by prepending self. This points to the instantiated object.
class Person:

  def __init__(self, name):
    self.name = name

  def greet(self):
    print(f"Hello! I'm {self.name}")

john = Person("John")
john.greet()
# Prints "Hello! I'm John"
  • This: In Java or C#, this is used instead of self. It fulfils the same functionality of pointing to the object’s members.
public class Person {

  private String name;

  public Person(String name) {
    this.name = name;
  }

  public void greet() {
    System.out.println("Hello! I'm " + this.name);
  }
}

Person john = new Person("John");
john.greet();
# Prints "Hello! I'm John"

Conclusion

Classes and objects are the key concepts in OOP, allowing modelling real-world entities and generating modular, generic components of our system to construct more robust and easy to understand programmes.

3.2 - Encapsulation

One of the fundamental pillars of object-oriented programming is encapsulation. This powerful characteristic allows us to control access to class members, hiding implementation details and protecting the state of our objects. In this article we will delve into the concept of encapsulation, the usefulness of getters, setters, public/private properties and methods, and the important benefits this provides us as developers.

The Oxford Dictionary defines encapsulation as “enclosed or contained as if in a capsule”. This is precisely what we seek to achieve - “packaging” data and code within a single capsule (the class) to hide its internal complexity.

The formal definition would be:

“Encapsulation refers to bundling data and functions that manipulate these data into a single entity or software module.”1

That is, keeping related data and behaviours together in order to restrict direct access to that data from other parts of the programme, interacting only through a controlled interface (public API).

This provides advantages like:

  • Control over data modification.
  • Flexibility to change internal parts without affecting others.
  • Protection of the consistent state of objects.
  • Hiding complexity from the user.

Let’s see with concrete examples how to encapsulate in OOP.


Getters and Setters

Say we have a BankAccount class, with properties like name, account number and balance:

class BankAccount:

    name = ""
    account_number = 0
    balance = 0.0

We can directly access attributes like:

account1 = BankAccount()
account1.name = "John"
account1.account_number = 1234
account1.balance = 2500

The problem is any other code can modify the balance to invalid values:

account1.balance = -9900 # Balance can't be negative in this bank!

This allows inconsistent state. To encapsulate we use getters and setters:

class BankAccount:

    def __init__(self):
        self.__balance = 0

    def get_balance(self):
        return self.__balance

    def set_balance(self, value):
        if value < 0:
            raise Exception("Balance can't be negative in this bank")
        self.__balance = value
  • __balance is now private. It is only manipulated via the public getters and setters.

  • The setter controls invalid values not being input.

In Python, prepending double underscore __ denotes a private method or attribute of the class. With a single underscore _ it’s by convention a protected element, accessible from class and subclasses but not externally. And with no underscores, methods and attributes are public.

In Java this is explicit using the keywords public, protected and private:

public class Person {

    private String name; // Private

    public String getName() { // Public
        return this.name;
    }

}

This notation helps declare the desired visibility to properly apply encapsulation.


Benefits of encapsulation

This powerful technique provides great advantages:

  • Information hiding: Implementation details are invisible to other objects, reducing coupling. Internal code can change minimizing impact.
  • Control over data: Integrity and validity of state is guaranteed via setters/validators.
  • Flexible code: Isolation between interfaces and specifics enables building more extensible and maintainable systems over time.

“Every module hides the complexity of its contents behind a simple fa??ade (interface)”, Gang of Four2.

Ultimately, when we need to control how internal state is manipulated in a class from other parts of the application, encapsulation is the best solution.


Conclusion

Applying encapsulation by restricting direct access to data and carefully coding a public access interface allows us to build more robust, secure and sustainable OOP systems over time.

Mastering these techniques requires experience and good judgement to find the right balance between information hiding and flexibility. But undoubtedly it???s worth the effort to leverage the benefits we???ve seen from this wonderful OOP principle.


References


  1. Byron, Jeff. Encapsulation in Java. https://stackify.com/encapsulation-in-java/ ↩︎

  2. Gamma, Erich et al. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. 1994. ↩︎