DP 1: Encapsulation - Getter Method in Python

Using Python’s @property to create a getter method
design patterns
Author

Tony Phung

Published

November 30, 2024

1. Introduction

This post is specifically related to Issue #22 of tonyjustdevs/learning_designpatterns:

2. Background

Encapsulation is the:

  • bundling of data (attributes) and
  • methods (functions)
  • into a single unit (class) and
  • restricting direct access to some of the object’s components.

Purpose:

  • To hide implementation details and
  • enforce controlled access to an object’s data.

Key Components:

  • Access modifiers (private, protected, public in some languages) and
  • Getters & Setters for controlled access.

2.1 Getters and Setters:

Definition: These are methods used to retrieve (get) and update (set) private or protected attributes of a class.

Purpose: To provide controlled access to the attributes while maintaining encapsulation.

Relation to Encapsulation: They implement the idea of “controlled access” in encapsulation.

They are tools to implement encapsulation.

3. Things To Do

  • Create Circle class
  • Instantiate a circle instance
  • Test attribute access directly: circle._radius
  • Test attribute access via getter1: circle.radius_accessor
  • Test attribute access via getter2: circle.radius

4. Create Circle class

  • create private variable: _radius
  • create getter method: radius_accessor()
  • decorate with: @property
class Circle():
  def __init__(self, name: str, radius: int):
    self.name: str = name
    self._radius: int = radius #21
  
  @property
  def radius_accessor(self):
    '''
    @property
    def radius_accessor(self):
    
    The name of this method becomes attribute name of an instance, or 
    (more accurately the attribute accesor?) 
    to access the private attribute we want 
    (usually defined in class init(): self._private_attribute)
    
    e.g. Suppose we have circle instance (type Circle)
    with private attr: circle._radius. 
    Instead of accessing it directly (circle._radius)
    which we can but we shouldn't, we access it via the getter created here 
    via @property with: circle.radius_accessor 
    
    i.e. circle.radius_accessor is getter for circle._radius
    
    Therefore, a more suitable method name for this getter would be:
    def radius():
    
    i.e.
    
    @property
    def radius(self) allows us to access circle._radius via circle.radius   
    '''
    return self._radius
  
  @property
  def radius(self):
    '''explained in radius_accessor()'''
    return self._radius
  
  def __repr__(self):
    return f"Circle(name={self.name!r}, _radius={self._radius!r})"
  
  def __str__(self):
    return f"It's a circle named {self.name!r} with a round belly of {self._radius} centimeters!"

4.1 Notes for Tony

print(Circle.radius_accessor.__doc__)

    @property
    def radius_accessor(self):
    
    The name of this method becomes attribute name of an instance, or 
    (more accurately the attribute accesor?) 
    to access the private attribute we want 
    (usually defined in class init(): self._private_attribute)
    
    e.g. Suppose we have circle instance (type Circle)
    with private attr: circle._radius. 
    Instead of accessing it directly (circle._radius)
    which we can but we shouldn't, we access it via the getter created here 
    via @property with: circle.radius_accessor 
    
    i.e. circle.radius_accessor is getter for circle._radius
    
    Therefore, a more suitable method name for this getter would be:
    def radius():
    
    i.e.
    
    @property
    def radius(self) allows us to access circle._radius via circle.radius   
    

5. Instantiate a circle instance

circle = Circle("Sir Cumference",50)
print(f"{circle}") # calls __str__
It's a circle named 'Sir Cumference' with a round belly of 50 centimeters!
circle # defaults to __repr__
Circle(name='Sir Cumference', _radius=50)

6. Test attribute access directly: circle._radius

circle._radius # still works - because private attr dont exist in python
50

7. Test attribute access via getter1: circle.radius_accessor

circle.radius_accessor
50

8. Test attribute access via getter2: circle.radius

circle.radius
50