-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy path01_classes_objects.py
More file actions
389 lines (298 loc) · 11.4 KB
/
01_classes_objects.py
File metadata and controls
389 lines (298 loc) · 11.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
"""
================================================================================
File: 01_classes_objects.py
Topic: Classes and Objects in Python
================================================================================
This file introduces Object-Oriented Programming (OOP) in Python, focusing on
classes and objects. Classes are blueprints for creating objects, which bundle
data (attributes) and functionality (methods) together.
Key Concepts:
- Defining classes
- Creating objects (instances)
- Instance attributes and methods
- Class attributes
- The self parameter
================================================================================
"""
# -----------------------------------------------------------------------------
# 1. What is a Class?
# -----------------------------------------------------------------------------
# A class is a blueprint for creating objects
print("--- What is a Class? ---")
# Simple class definition
class Dog:
"""A simple class representing a dog."""
pass # Empty class (for now)
# Creating objects (instances) from the class
dog1 = Dog()
dog2 = Dog()
print(f"dog1 is a: {type(dog1)}")
print(f"dog1 and dog2 are different objects: {dog1 is not dog2}")
# -----------------------------------------------------------------------------
# 2. Instance Attributes
# -----------------------------------------------------------------------------
# Each object can have its own data
print("\n--- Instance Attributes ---")
class Person:
"""A class representing a person."""
def __init__(self, name, age):
"""Initialize person with name and age."""
# self refers to the instance being created
self.name = name
self.age = age
# Creating instances with attributes
person1 = Person("Alice", 25)
person2 = Person("Bob", 30)
print(f"Person 1: {person1.name}, age {person1.age}")
print(f"Person 2: {person2.name}, age {person2.age}")
# Modifying attributes
person1.age = 26
print(f"After birthday: {person1.name} is now {person1.age}")
# Adding new attributes to instance
person1.email = "alice@example.com"
print(f"Added email: {person1.email}")
# -----------------------------------------------------------------------------
# 3. Instance Methods
# -----------------------------------------------------------------------------
# Functions defined inside a class that operate on instances
print("\n--- Instance Methods ---")
class Rectangle:
"""A class representing a rectangle."""
def __init__(self, width, height):
"""Initialize rectangle dimensions."""
self.width = width
self.height = height
def area(self):
"""Calculate the area of the rectangle."""
return self.width * self.height
def perimeter(self):
"""Calculate the perimeter of the rectangle."""
return 2 * (self.width + self.height)
def describe(self):
"""Return a description of the rectangle."""
return f"Rectangle {self.width}x{self.height}"
# Using methods
rect = Rectangle(5, 3)
print(f"Rectangle: {rect.describe()}")
print(f"Area: {rect.area()}")
print(f"Perimeter: {rect.perimeter()}")
# -----------------------------------------------------------------------------
# 4. The self Parameter
# -----------------------------------------------------------------------------
# self refers to the current instance
print("\n--- Understanding self ---")
class Counter:
"""A class demonstrating self."""
def __init__(self):
"""Initialize counter to 0."""
self.count = 0
def increment(self):
"""Increase count by 1."""
self.count += 1 # self.count refers to this instance's count
return self
def decrement(self):
"""Decrease count by 1."""
self.count -= 1
return self
def reset(self):
"""Reset count to 0."""
self.count = 0
return self
def get_count(self):
"""Return current count."""
return self.count
counter1 = Counter()
counter2 = Counter()
counter1.increment()
counter1.increment()
counter1.increment()
counter2.increment()
print(f"Counter 1: {counter1.get_count()}") # 3
print(f"Counter 2: {counter2.get_count()}") # 1 - separate instance!
# Method chaining (returning self)
counter1.reset().increment().increment()
print(f"After chaining: {counter1.get_count()}")
# -----------------------------------------------------------------------------
# 5. Class Attributes
# -----------------------------------------------------------------------------
# Attributes shared by all instances of a class
print("\n--- Class Attributes ---")
class Car:
"""A class with class attributes."""
# Class attribute - shared by all instances
wheels = 4
count = 0 # Track number of cars created
def __init__(self, brand, model):
"""Initialize car with brand and model."""
self.brand = brand # Instance attribute
self.model = model # Instance attribute
Car.count += 1 # Increment class attribute
def describe(self):
"""Describe the car."""
return f"{self.brand} {self.model} ({Car.wheels} wheels)"
# Creating cars
car1 = Car("Toyota", "Camry")
car2 = Car("Honda", "Civic")
car3 = Car("Ford", "Mustang")
print(f"Car 1: {car1.describe()}")
print(f"Car 2: {car2.describe()}")
print(f"Total cars created: {Car.count}")
# Accessing class attribute from class or instance
print(f"Car.wheels: {Car.wheels}")
print(f"car1.wheels: {car1.wheels}")
# Modifying class attribute
Car.wheels = 6 # Now all cars have 6 wheels
print(f"After modification - car1.wheels: {car1.wheels}")
print(f"After modification - car2.wheels: {car2.wheels}")
# -----------------------------------------------------------------------------
# 6. Private Attributes (Convention)
# -----------------------------------------------------------------------------
# Python uses naming conventions for privacy
print("\n--- Private Attributes ---")
class BankAccount:
"""A class demonstrating private attributes."""
def __init__(self, owner, balance=0):
"""Initialize bank account."""
self.owner = owner # Public
self._balance = balance # Protected (convention)
self.__id = id(self) # Private (name mangling)
def deposit(self, amount):
"""Deposit money into account."""
if amount > 0:
self._balance += amount
return True
return False
def withdraw(self, amount):
"""Withdraw money from account."""
if 0 < amount <= self._balance:
self._balance -= amount
return True
return False
def get_balance(self):
"""Get current balance."""
return self._balance
account = BankAccount("Alice", 1000)
account.deposit(500)
account.withdraw(200)
print(f"Account owner: {account.owner}")
print(f"Balance: {account.get_balance()}")
# Can still access protected (but shouldn't)
print(f"Protected _balance: {account._balance}")
# Mangled name for private
print(f"Mangled private __id: {account._BankAccount__id}")
# -----------------------------------------------------------------------------
# 7. Methods that Return New Objects
# -----------------------------------------------------------------------------
print("\n--- Returning Objects ---")
class Point:
"""A class representing a 2D point."""
def __init__(self, x, y):
"""Initialize point coordinates."""
self.x = x
self.y = y
def move(self, dx, dy):
"""Return a new point moved by (dx, dy)."""
return Point(self.x + dx, self.y + dy)
def distance_to(self, other):
"""Calculate distance to another point."""
return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
def __str__(self):
"""String representation."""
return f"Point({self.x}, {self.y})"
p1 = Point(0, 0)
p2 = p1.move(3, 4)
print(f"Original point: {p1}")
print(f"New point: {p2}")
print(f"Distance: {p1.distance_to(p2)}")
# -----------------------------------------------------------------------------
# 8. Special Methods (Dunder Methods)
# -----------------------------------------------------------------------------
# Methods with double underscores have special meanings
print("\n--- Special Methods ---")
class Vector:
"""A class demonstrating special methods."""
def __init__(self, x, y):
"""Initialize vector."""
self.x = x
self.y = y
def __str__(self):
"""Human-readable string representation."""
return f"Vector({self.x}, {self.y})"
def __repr__(self):
"""Developer representation (for debugging)."""
return f"Vector(x={self.x}, y={self.y})"
def __len__(self):
"""Length (in this case, magnitude as int)."""
return int((self.x ** 2 + self.y ** 2) ** 0.5)
def __add__(self, other):
"""Add two vectors."""
return Vector(self.x + other.x, self.y + other.y)
def __eq__(self, other):
"""Check equality."""
return self.x == other.x and self.y == other.y
v1 = Vector(3, 4)
v2 = Vector(1, 2)
v3 = v1 + v2 # Uses __add__
print(f"v1: {v1}") # Uses __str__
print(f"v1 + v2 = {v3}") # Uses __add__ and __str__
print(f"len(v1): {len(v1)}") # Uses __len__
print(f"v1 == Vector(3, 4): {v1 == Vector(3, 4)}") # Uses __eq__
# -----------------------------------------------------------------------------
# 9. Practical Example: Student Class
# -----------------------------------------------------------------------------
print("\n--- Practical Example: Student ---")
class Student:
"""A class representing a student."""
school_name = "Python Academy" # Class attribute
def __init__(self, name, student_id):
"""Initialize student."""
self.name = name
self.student_id = student_id
self.grades = []
def add_grade(self, grade):
"""Add a grade (0-100)."""
if 0 <= grade <= 100:
self.grades.append(grade)
return True
return False
def get_average(self):
"""Calculate grade average."""
if not self.grades:
return 0
return sum(self.grades) / len(self.grades)
def get_letter_grade(self):
"""Get letter grade based on average."""
avg = self.get_average()
if avg >= 90: return 'A'
if avg >= 80: return 'B'
if avg >= 70: return 'C'
if avg >= 60: return 'D'
return 'F'
def __str__(self):
"""String representation."""
return f"{self.name} (ID: {self.student_id}) - {self.school_name}"
# Using the Student class
student = Student("Baraa", "S12345")
student.add_grade(85)
student.add_grade(90)
student.add_grade(78)
print(student)
print(f"Grades: {student.grades}")
print(f"Average: {student.get_average():.1f}")
print(f"Letter Grade: {student.get_letter_grade()}")
# -----------------------------------------------------------------------------
# 10. Summary
# -----------------------------------------------------------------------------
print("\n--- Summary ---")
summary = """
Key OOP concepts:
- Class: Blueprint for objects
- Object: Instance of a class
- self: Reference to current instance
- __init__: Constructor method
- Instance attributes: Unique to each object
- Class attributes: Shared by all instances
- Methods: Functions that belong to a class
- Special methods: __str__, __repr__, __add__, etc.
"""
print(summary)