# Nakafa Learning Content

> For AI agents: use [llms.txt](https://nakafa.com/llms.txt) for the site index. Markdown versions are available by appending `.md` to content URLs or sending `Accept: text/markdown`.

URL: https://nakafa.com/en/subjects/ai-ds/ai-programming/everything-object-python
Source: https://raw.githubusercontent.com/nakafaai/nakafa.com/refs/heads/main/packages/contents/material/lesson/ai-ds/ai-programming/everything-object-python/en.mdx

Learn Python's object-oriented nature and how numbers, strings, lists, and functions become objects with methods and attributes.

---

## Basic Object-Oriented Programming Concepts

Python is a programming language that adopts the object-oriented programming or OOP paradigm. One of Python's fundamental principles is that everything is an object. This concept might sound abstract at first, but it's actually very natural and similar to how we view the real world.

Imagine the world around us. All objects have characteristics and can do something. A car has color, brand, and speed, and can move, stop, and honk its horn. Similarly in Python, every data and function is an object that has attributes and can perform certain actions.

## Programming Paradigm Comparison

### Procedural Languages

In procedural programming languages, state and actions are separated. Data and functions that operate on that data are separate from each other.

File: procedural_parking.c
```c
// Example of procedural approach in C language
struct ParkSystem {
  int max;
  int occ;
};

int occupy(struct ParkSystem* ps) {
  if (ps->max <= ps->occ) {
      return -1;
  }
  ps->occ++;
  return 0;
}

void leave(struct ParkSystem* ps) {
  if (ps->occ > 0) {
      ps->occ--;
  }
}

int main() {
  struct ParkSystem ps = {100, 0};
  occupy(&ps);
  printf("%d %d", ps.max, ps.occ);
  return 0;
}
```

In the procedural approach, you can see that data (struct) and functions are separate. Functions accept data as parameters and modify them from outside.

### Object-Oriented Languages

In object-oriented programming languages, state and actions are combined into one unit called a class. Attributes describe state, while methods describe actions that can be performed on that state.

File: oop_parking.py
```python
# Example of object-oriented approach in Python
class ParkSystem:
  def __init__(self, max_capacity):
      self.max = max_capacity
      self.occ = 0

  def occupy(self):
      if self.max <= self.occ:
          return -1
      self.occ += 1
      return 0

  def leave(self):
      if self.occ > 0:
          self.occ -= 1

# Usage
ps = ParkSystem(100)
ps.occupy()
print(f"Max: {ps.max}, Occupied: {ps.occ}")
```

In the object-oriented approach, data and functions that operate on that data are packaged in one unit. Objects have the responsibility to manage their own state.

## Parking System as an Example

Let's use a parking system as an example to understand the concept of objects. In the real world, a parking system has characteristics like maximum capacity and number of occupied spaces. This system can also perform actions like accepting cars entering or leaving.

File: parking_system_detailed.py
```python
class ParkingSystem:
  def __init__(self, capacity):
      """Initialize parking system with certain capacity"""
      self.capacity = capacity
      self.occupied = 0
      self.vehicles = []  # List of parked vehicles

  def park_vehicle(self, vehicle_plate):
      """Method to park a vehicle"""
      if self.occupied >= self.capacity:
          return f"Parking full! Cannot park {vehicle_plate}"

      self.vehicles.append(vehicle_plate)
      self.occupied += 1
      return f"Vehicle {vehicle_plate} successfully parked"

  def exit_vehicle(self, vehicle_plate):
      """Method to exit parking"""
      if vehicle_plate in self.vehicles:
          self.vehicles.remove(vehicle_plate)
          self.occupied -= 1
          return f"Vehicle {vehicle_plate} exited parking"
      else:
          return f"Vehicle {vehicle_plate} not found"

  def get_status(self):
      """Method to view parking status"""
      available = self.capacity - self.occupied
      return {
          'capacity': self.capacity,
          'occupied': self.occupied,
          'available': available,
          'vehicles': self.vehicles
      }

  def is_full(self):
      """Method to check if parking is full"""
      return self.occupied >= self.capacity

# Example usage
parking = ParkingSystem(5)
print(parking.park_vehicle("B 1234 CD"))
print(parking.park_vehicle("B 5678 EF"))
print(parking.get_status())
print(parking.is_full())
```

## Object Concepts in Data Types

In Python, the concept of "everything is an object" means every data you create has attributes and methods. Let's explore this concept deeper.

### Numbers are Objects

File: numbers_as_objects.py
```python
# Numbers in Python are objects
number = 42

# Numbers have methods
print(number.bit_length())  # Method to calculate bit length
print(number.__add__(8))    # Method for addition (same as number + 8)

# Float numbers are also objects
price = 15.75
print(price.is_integer())   # Method to check if it's a whole number
print(price.as_integer_ratio())  # Method to convert to ratio

# Even mathematical operation results are objects
result = 10 + 5
print(type(result))         # <class 'int'>
print(dir(result))          # View all available methods
```

### Strings are Objects

File: strings_as_objects.py
```python
# Strings in Python are very powerful objects
message = "Learning Python is Fun"

# Strings have many useful methods
print(message.upper())              # LEARNING PYTHON IS FUN
print(message.lower())              # learning python is fun
print(message.title())              # Learning Python Is Fun
print(message.count('n'))           # Count letter 'n'
print(message.replace('Python', 'Programming'))  # Replace word

# Methods for checking
email = "user@example.com"
print(email.endswith('.com'))       # True
print(email.startswith('user'))     # True
print(email.find('@'))              # Position of '@' character

# Methods for formatting
name = "alice"
age = 25
formatted = "Name: {}, Age: {}".format(name.title(), age)
print(formatted)

# String split and join
words = message.split()             # Split into word list
print(words)
rejoined = " ".join(words)          # Join back together
print(rejoined)
```

### Lists are Objects

File: lists_as_objects.py
```python
# Lists in Python are dynamic objects
fruits = ["apple", "orange", "mango"]

# Lists have methods for data manipulation
fruits.append("banana")             # Add at the end
print(fruits)

fruits.insert(1, "strawberry")      # Add at specific position
print(fruits)

fruits.remove("orange")             # Remove specific item
print(fruits)

# Methods for searching and sorting
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print(numbers.count(1))             # Count occurrences
print(numbers.index(4))             # Find position

numbers.sort()                      # Sort
print(numbers)

numbers.reverse()                   # Reverse order
print(numbers)

# List comprehension - pythonic way
squares = [x**2 for x in range(1, 6)]
print(squares)                      # [1, 4, 9, 16, 25]

# Methods for advanced operations
original = [1, 2, 3]
copy_list = original.copy()         # Create copy
original.extend([4, 5])             # Add multiple items
print(f"Original: {original}")
print(f"Copy: {copy_list}")
```

### Dictionaries are Objects

File: dicts_as_objects.py
```python
# Dictionaries in Python are flexible objects
student = {
  "name": "Alice",
  "age": 22,
  "major": "Computer Science",
  "gpa": 3.8
}

# Dictionaries have methods for manipulation
print(student.get("name"))          # Get value safely
print(student.get("height", "Unknown"))  # Default value if key doesn't exist

# Methods to view dictionary contents
print(student.keys())               # All keys
print(student.values())             # All values
print(student.items())              # Key-value pairs

# Methods for update and manipulation
student.update({"semester": 6, "age": 23})  # Update multiple values
print(student)

# Pop method to get and remove
gpa = student.pop("gpa")
print(f"Removed GPA: {gpa}")
print(student)

# Dictionary comprehension
grades = {"Math": 85, "Physics": 92, "Chemistry": 78}
passed = {subject: grade for subject, grade in grades.items() if grade >= 80}
print(passed)

# Nested dictionary
university = {
  "students": {
      "CS": ["Alice", "Bob"],
      "Math": ["Charlie", "Diana"]
  },
  "location": "Jakarta"
}
print(university["students"]["CS"])
```

## Functions are Also Objects

One interesting thing about Python is that functions are also objects. This allows you to treat functions like regular data.

File: functions_as_objects.py
```python
# Functions in Python are first-class objects
def greet(name):
  return f"Hello, {name}!"

def farewell(name):
  return f"Goodbye, {name}!"

# Functions have attributes
print(greet.__name__)               # Function name
print(type(greet))                  # <class 'function'>

# Functions can be stored in variables
my_function = greet
print(my_function("Alice"))         # Hello, Alice!

# Functions can be stored in lists
functions = [greet, farewell]
for func in functions:
  print(func("Bob"))

# Functions can be used as parameters
def call_function(func, name):
  return func(name)

result = call_function(greet, "Charlie")
print(result)

# Functions can be returned from other functions
def get_greeting_function(language):
  def english_greet(name):
      return f"Hello, {name}!"

  def indonesian_greet(name):
      return f"Halo, {name}!"

  if language == "english":
      return english_greet
  else:
      return indonesian_greet

# Using returned function
greet_func = get_greeting_function("indonesian")
print(greet_func("Diana"))          # Halo, Diana!

# Lambda functions are also objects
square = lambda x: x ** 2
print(type(square))                 # <class 'function'>
print(square(5))                    # 25
```

## Classes and Instances as Objects

A class is a blueprint for creating objects, while an instance is an object created from that class.

File: class_and_instances.py
```python
# Creating class as blueprint
class Student:
  # Class attribute (shared by all instances)
  university = "University of Indonesia"

  def __init__(self, name, major, semester):
      # Instance attributes (unique for each instance)
      self.name = name
      self.major = major
      self.semester = semester
      self.courses = []

  def add_course(self, course):
      """Method to add a course"""
      self.courses.append(course)
      return f"{self.name} is taking course {course}"

  def get_info(self):
      """Method to get student information"""
      return {
          'name': self.name,
          'major': self.major,
          'semester': self.semester,
          'courses': self.courses,
          'university': self.university
      }

  def __str__(self):
      """Special method for string representation"""
      return f"Student({self.name}, {self.major})"

  def __len__(self):
      """Special method to get number of courses"""
      return len(self.courses)

# Creating instances (objects) from class
student1 = Student("Alice", "Computer Science", 4)
student2 = Student("Bob", "Mathematics", 6)

# Each instance is an object with attributes and methods
print(student1.add_course("Python Programming"))
print(student1.add_course("Data Structures"))

print(student2.add_course("Calculus"))
print(student2.add_course("Linear Algebra"))

# Using methods
print(student1.get_info())
print(student2.get_info())

# Special methods (__str__ and __len__)
print(student1)                     # Student(Alice, Computer Science)
print(len(student1))                # 2 (number of courses)

# Instances have accessible attributes
print(f"Name: {student1.name}")
print(f"Major: {student1.major}")
print(f"University: {student1.university}")

# Class is also an object
print(type(Student))                # <class 'type'>
print(Student.__name__)             # Student
print(Student.university)           # University of Indonesia
```

## Special Methods

Python has special methods that start and end with double underscores. These methods allow your objects to behave like built-in types.

File: magic_methods.py
```python
class BankAccount:
  def __init__(self, owner, balance=0):
      self.owner = owner
      self.balance = balance

  def __str__(self):
      """Readable string representation"""
      return f"BankAccount({self.owner}, USD{self.balance:,})"

  def __repr__(self):
      """String representation for debugging"""
      return f"BankAccount('{self.owner}', {self.balance})"

  def __len__(self):
      """Return length of owner name"""
      return len(self.owner)

  def __eq__(self, other):
      """Compare two bank accounts"""
      if isinstance(other, BankAccount):
          return self.balance == other.balance
      return False

  def __lt__(self, other):
      """Less than comparison"""
      if isinstance(other, BankAccount):
          return self.balance < other.balance
      return False

  def __add__(self, amount):
      """Add balance (deposit)"""
      if isinstance(amount, (int, float)) and amount > 0:
          self.balance += amount
      return self

  def __sub__(self, amount):
      """Subtract balance (withdraw)"""
      if isinstance(amount, (int, float)) and amount > 0:
          if amount <= self.balance:
              self.balance -= amount
          else:
              print("Insufficient balance!")
      return self

  def __bool__(self):
      """True if has balance, False if balance is 0"""
      return self.balance > 0

# Creating objects
account1 = BankAccount("Alice", 1000000)
account2 = BankAccount("Bob", 500000)

# Magic methods in action
print(account1)                     # __str__
print(repr(account1))               # __repr__
print(len(account1))                # __len__ (name length)

# Comparison
print(account1 == account2)         # __eq__
print(account1 > account2)          # __lt__ (reversed)

# Arithmetic operations
account1 + 500000                   # __add__ (deposit)
print(account1)

account1 - 200000                   # __sub__ (withdraw)
print(account1)

# Boolean conversion
empty_account = BankAccount("Charlie", 0)
print(bool(account1))               # True
print(bool(empty_account))          # False

# Using in conditionals
if account1:
  print(f"{account1.owner} has balance")
else:
  print(f"{account1.owner} has no balance")
```

## Object-Oriented Programming Practice

Let's create a more complex example to understand how OOP is applied in real scenarios.

File: library_management.py
```python
class Book:
  def __init__(self, title, author, isbn):
      self.title = title
      self.author = author
      self.isbn = isbn
      self.is_borrowed = False
      self.borrower = None

  def __str__(self):
      status = "Borrowed" if self.is_borrowed else "Available"
      return f"{self.title} by {self.author} - {status}"

class Member:
  def __init__(self, name, member_id):
      self.name = name
      self.member_id = member_id
      self.borrowed_books = []

  def __str__(self):
      return f"Member: {self.name} (ID: {self.member_id})"

class Library:
  def __init__(self, name):
      self.name = name
      self.books = []
      self.members = []

  def add_book(self, book):
      """Add book to library"""
      self.books.append(book)
      return f"Book '{book.title}' successfully added"

  def register_member(self, member):
      """Register new member"""
      self.members.append(member)
      return f"Member {member.name} successfully registered"

  def borrow_book(self, member_id, isbn):
      """Borrow book"""
      # Find member
      member = None
      for m in self.members:
          if m.member_id == member_id:
              member = m
              break

      if not member:
          return "Member not found"

      # Find book
      book = None
      for b in self.books:
          if b.isbn == isbn and not b.is_borrowed:
              book = b
              break

      if not book:
          return "Book not available"

      # Process borrowing
      book.is_borrowed = True
      book.borrower = member
      member.borrowed_books.append(book)

      return f"{member.name} successfully borrowed '{book.title}'"

  def return_book(self, member_id, isbn):
      """Return book"""
      member = None
      for m in self.members:
          if m.member_id == member_id:
              member = m
              break

      if not member:
          return "Member not found"

      book = None
      for b in member.borrowed_books:
          if b.isbn == isbn:
              book = b
              break

      if not book:
          return "Book not found in borrowed list"

      # Process return
      book.is_borrowed = False
      book.borrower = None
      member.borrowed_books.remove(book)

      return f"{member.name} successfully returned '{book.title}'"

  def get_available_books(self):
      """Get list of available books"""
      available = [book for book in self.books if not book.is_borrowed]
      return available

  def get_member_books(self, member_id):
      """Get list of books borrowed by member"""
      for member in self.members:
          if member.member_id == member_id:
              return member.borrowed_books
      return []

# Example usage of library system
library = Library("Central Library")

# Adding books
book1 = Book("Python Programming", "John Smith", "978-1234567890")
book2 = Book("Data Science Basics", "Jane Doe", "978-0987654321")
book3 = Book("Machine Learning", "Bob Johnson", "978-1122334455")

print(library.add_book(book1))
print(library.add_book(book2))
print(library.add_book(book3))

# Registering members
member1 = Member("Alice Cooper", "M001")
member2 = Member("Bob Wilson", "M002")

print(library.register_member(member1))
print(library.register_member(member2))

# Borrowing books
print(library.borrow_book("M001", "978-1234567890"))
print(library.borrow_book("M002", "978-0987654321"))

# Viewing book status
print("\nList of all books:")
for book in library.books:
  print(book)

print("\nAvailable books:")
for book in library.get_available_books():
  print(book)

print(f"\nBooks borrowed by Alice: {len(library.get_member_books('M001'))} books")

# Returning books
print(library.return_book("M001", "978-1234567890"))
```

By understanding the concept that everything is an object in Python, you can write code that is more structured, easier to understand, and easier to maintain. Object-oriented programming allows you to model real-world problems into code in a natural and intuitive way.