python_catchup_2024/3_advanced_classes.py
2024-11-08 20:47:03 +00:00

91 lines
3.3 KiB
Python

"""
Now that we know the basics of classes, variables, and functions, we will move on to more
abstract ideas.
I'm also going to introduce type hinting; this reduces ambiguity and improves readability.
Key Concepts:
**Encapsulation** is the concept of grouping related data and methods within a
class and restricting access to them to control how they are used.
**Access Modifiers** are used to determine who/what can access a certain attribute or method.
- **Public**: Accessible from any part of the code. Used for attributes and methods intended to be part
of the class's interface for external use.
- **Protected**: Intended for internal use within the class and its subclasses. "_value" represents it.
Useful for attributes/methods that should be accessible in subclasses but not publicly.
- **Private**: Only accessible within the class, represented by "__value". Useful for securing sensitive
data or internal methods that should not be altered externally.
IMPORTANT: Python does not strictly enforce access control, so these conventions mainly serve as guidelines.
"""
# This is the abstraction library in Python
from abc import ABC, abstractmethod
# Type hinting, not needed but reduces ambiguity. Note: not enforced at runtime.
from typing import Optional
# An abstract class, provides a blueprint for other classes to inherit.
class Shape(ABC):
# This method is required in all subclasses, ensuring they provide their own area calculation.
@abstractmethod
def area(self) -> float:
"""Implemented to ensure each subclass contains this method."""
class Rectangle(Shape):
def __init__(self, width: float, height: float):
# A private attribute to store width.
self.__width = width
# A private attribute to store height.
self.__height = height
# This allows access to the private 'width' attribute in a controlled way.
@property
def width(self) -> float:
return self.__width
# This allows you to set the value of 'width' after initialization.
@width.setter
def width(self, value: float):
if value <= 0:
raise ValueError("Width must be positive.")
self.__width = value
# This allows access to the private 'height' attribute in a controlled way.
@property
def height(self) -> float:
return self.__height
# This allows you to set the value of 'height' after initialization.
@height.setter
def height(self, value: float):
if value <= 0:
raise ValueError("Height must be positive.")
self.__height = value
def area(self) -> float:
# Calculates the area of the rectangle.
return self.__width * self.__height
def set_dimensions(self, width: Optional[float] = None, height: Optional[float] = None):
if width is not None:
# Sets 'width' using the setter.
self.width = width
if height is not None:
# Sets 'height' using the setter.
self.height = height
rect = Rectangle(5, 10)
# Prints the area of the rectangle (5 * 10 = 50).
print(rect.area())
# Updates 'width' using the setter, height remains the same.
rect.set_dimensions(width=8)
# Prints the new area of the rectangle (8 * 10 = 80).
print(rect.area())