โณ Loading Python Engine...

๐Ÿ“Š Day 17 : OOP Advanced

๐ŸŽฏ Enterprise Objective

To write truly Pythonic code, your custom classes should behave like built-in types. Today we unlock 'Dunder' magic methods to make our objects printable, measurable, and iterable. We also explore the elegant @property decorator for bulletproof data validation.

๐Ÿ“‹ Strategic Overview

#TopicConcept
1Dundersstr, len, eq
2PropertiesEncapsulation, @property
3Class Methods@classmethod, @staticmethod

1. Dunder Methods : Magic Integration

๐Ÿ” What is it?

Dunder (Double Under) methods are special methods like str or len that allow your objects to integrate seamlessly with Python's built-in functions. They define how an object should behave when printed, added, or measured.

MethodTriggered ByPurpose
strprint(obj)User-friendly string representation
reprrepr(obj)Developer-friendly representation (for debugging)
lenlen(obj)Return length or size of object
eqobj == otherCustom equality checking

๐Ÿ’ผ Why Data Analysts Care

โ€ข Beautiful Logging: Implementing str so print(user) shows 'Alice' instead of <main.User object at 0x...>

โ€ข Custom Collections: Implementing len on a custom Dataset class so len(df) works naturally

โš ๏ธ Missing __repr__

Always implement at least repr. If str is missing, Python falls back to repr. If both are missing, you get the ugly memory address string.

In [ ]:

๐Ÿงช Concept Checks: Dunder Methods

Q1. Create a Point(x,y) class. Try to print() an instance and note the ugly output.

In [ ]:

Q2. Add a str method to Point that returns "(x, y)". Print the instance again.

In [ ]:

Q3. Add an eq method that returns True if both x and y are the same. Test Point(1,1) == Point(1,1).

In [ ]:

Q4. Add a add method that allows you to add two points together: Point(x1+x2, y1+y2). Test p1 + p2.

In [ ]:

Q5. Why is repr meant for developers and str meant for users?

In [ ]:

2. Properties & Encapsulation : Safe Data Access

๐Ÿ” What is it?

Encapsulation is the practice of hiding internal state. In Python, we prefix private variables with an underscore _. To provide controlled access to these variables, we use the @property decorator, which allows methods to be accessed like attributes.

class User:
    def __init__(self):
        self._score = 0  # Private variable
        
    @property
    def score(self):
        return self._score # Accessed without ()

๐Ÿ’ผ Why Data Analysts Care

โ€ข Data Validation: Preventing a user's age from being set to a negative number via a property setter

โ€ข Computed Properties: Calculating fullname dynamically from first_name and last_name without storing it

๐Ÿง  Pro Tip

Python doesn't have strict 'private' variables like Java. The underscore is a 'gentleman's agreement' among developers: 'This is internal, do not touch it directly.'

In [ ]:

๐Ÿงช Concept Checks: Properties

Q1. Create a Person class with an init that sets self.first and self.last.

In [ ]:

Q2. Add a @property method called fullname that returns first + " " + last.

In [ ]:

Q3. Create an instance, change first, and then print fullname without parentheses. See how it updates.

In [ ]:

Q4. Add a setter @fullname.setter that takes a full name string, splits it, and updates first and last.

In [ ]:

Q5. Why are properties better than Java-style get_name() and set_name() methods?

In [ ]:

3. Class & Static Methods : Alternative Constructors

๐Ÿ” What is it?
@classmethod takes cls instead of self and modifies class-level state. It's heavily used for alternative constructors. @staticmethod takes neither and is just a regular function that is logically bundled inside the class.
DecoratorFirst ArgPurpose
(None)selfManipulate instance state
@classmethodclsManipulate class state / Alternative constructors
@staticmethod(None)Utility functions that don't need class/instance state

๐Ÿ’ผ Why Data Analysts Care

โ€ข API Parsers: User.from_json(json_string) is a classic use of @classmethod to instantiate objects from APIs

โ€ข Utilities: MathUtils.is_prime(n) is a classic @staticmethod

๐Ÿง  Pro Tip

If your method doesn't use self anywhere inside it, your IDE will usually suggest converting it to a @staticmethod.

In [ ]:

๐Ÿงช Concept Checks: Class Methods

Q1. Create a Date class with day, month, year. Add a @classmethod from_string(cls, date_str) that parses "DD-MM-YYYY".

In [ ]:

Q2. Test the class method: d = Date.from_string("25-12-2023"). Print its year.

In [ ]:

Q3. Add a @staticmethod is_valid_month(m) that checks if 1 <= m <= 12. Test it.

In [ ]:

Q4. Explain the difference between cls in a class method and self in an instance method.

In [ ]:

Q5. Add a class attribute count = 0. Increment it inside init. Create 3 objects and print Date.count.

In [ ]:

๐Ÿ› ๏ธ Professional Practice Tasks

Theory is useless without muscle memory. Complete these tasks to solidify your understanding.

Task 1 (Vector Math): Create a Vector(x, y) class. Implement add (add two vectors), sub (subtract), and str (print like ). Test them.

In [ ]:

Task 2 (Safe Wallet): Create a Wallet class. Add a private _balance starting at 0. Add a @property for balance. Add a @balance.setter that prevents setting the balance to a negative number. Raise ValueError if attempted.

In [ ]:

Task 3 (DataFrame Mock): Create a MockDF class initialized with a list of dictionaries. Implement len to return row count. Implement getitem(key) to return a list of values for a specific column key.

In [ ]:

Task 4 (User Parser): Create a User(username, email) class. Add a @classmethod from_csv(cls, csv_line) that takes a string 'bob,bob@gmail.com', splits it, and returns a new User instance.

In [ ]:

Task 5 (String Utilities): Create a StringUtils class. Add @staticmethods for is_palindrome(s) and count_vowels(s). Call them without instantiating the class.

In [ ]:

๐Ÿ’ป Pure Coding Interview Questions

Q1.

Write a Point(x, y) class. Implement str to return '(x, y)' and repr to return 'Point(x, y)'. Print both.

In [ ]:

Q2.

Write a Vector(x, y) class that implements add to add two vectors. Test with Vector(1,2) + Vector(3,4) and print.

In [ ]:

Q3.

Write a class Config that implements getitem and setitem using an internal dict. Test with cfg['key'] = 'value'.

In [ ]:

Q4.

Write a Temperature class with a @property celsius and a computed @property fahrenheit. Demonstrate updating celsius and reading fahrenheit.

In [ ]:

Q5.

Write a Date class with @classmethod from_string(cls, s) and @staticmethod is_valid_year(y). Demonstrate both.

In [ ]:

Q6.

Write a User class with @classmethod from_json(cls, json_str) that parses a JSON string into a User object. Test it.

In [ ]:

Q7.

Write a class with __name (double underscore). Print dir(obj) to find the mangled name _ClassName__name. Access it.

In [ ]:

Q8.

Write a Timer context manager class implementing enter and exit. Use it with with Timer(): time.sleep(0.5).

In [ ]:

Q9.

Write a simple descriptor class PositiveNumber with get and set that rejects negative values. Use it in a Product class for price.

In [ ]:

Q10.

Write a class implementing iter and next to iterate over a range of even numbers. Test with for n in EvenRange(10):.

In [ ]:

Q11.

Write a Greeter class with call(self, name) so instances can be called like functions: g = Greeter(); g('Alice').

In [ ]:

Q12.

Write a Singleton class using new: override it to return the same instance every time. Create two objects and verify a is b.

In [ ]:

Q13.

Write a Singleton class overriding new to always return the same instance. Create a = S(); b = S() and print a is b.

In [ ]:

Q14.

Write a class using slots = ['x', 'y']. Try to add a dynamic attribute obj.z = 1 and catch the AttributeError.

In [ ]:

Q15.

Write code comparing memory usage of a class with slots vs without using sys.getsizeof(). Print both sizes.

In [ ]:

Q16.

Write a class with getattr that returns 'not found' for any missing attribute. Test with obj.nonexistent.

In [ ]:

Q17.

Write a class with getattr (missing attrs only) vs getattribute (all attr access). Show how the latter intercepts everything.

In [ ]:

Q18.

Write a class with del that prints 'Deleted'. Create an instance, delete it with del obj, and observe the output.

In [ ]:

Q19.

Write a Person class with _age. Add a @age.setter that raises TypeError if the value is not an int. Test it.

In [ ]:

Q20.

Write two unrelated classes with len and getitem. Pass both to a function that calls len() and [0] on them โ€” demonstrating duck typing.

In [ ]:

Q21.

Write a class HexInt(int) that overrides str to return the hex representation. Test with print(HexInt(255)).

In [ ]:

Q22.

Import abc. Write an ABC Shape with @abstractmethod area(). Create Circle(Shape) implementing area(). Show what happens if you don't implement it.

In [ ]:

Q23.

Write an ABC DataReader with @abstractmethod read(). Write CSVReader(DataReader) and JSONReader(DataReader) implementing it.

In [ ]:

Q24.

Write a class with diamond inheritance (A -> B, A -> C, B,C -> D). Call D.mro() and print the resolution order.

In [ ]:

Q25.

Write a class using @functools.cached_property for an expensive computation. Show it only computes once by adding a print statement inside.

In [ ]:

๐Ÿ“Š Day 17 Executive Summary

#TopicKey Takeaway
1DunderMakes objects act like native Python types
2PropertyProtects private data while keeping syntax clean
3ClassmethodPerfect for alternative constructors (from_json)

โœ… Instructor's End-of-Day Checklist

โ€ข [ ] I can implement str and repr.

โ€ข [ ] I can use @property for getter/setter validation.

โ€ข [ ] I understand when to use @classmethod.