Suppose you have a dataset containing movie ratings, and you want to perform some data analysis on it. You can create a MovieRatings
class with a ratings
attribute representing the list of movie ratings. We'll use a getter to calculate statistics like average rating and a setter to validate and filter out invalid ratings.
class MovieRatings:
def __init__(self, ratings):
self._ratings = ratings
@property
def ratings(self):
return self._ratings
@ratings.setter
def ratings(self, new_ratings):
valid_ratings = [rating for rating in new_ratings if 0 <= rating <= 10]
self._ratings = valid_ratings
@property
def average_rating(self):
return sum(self._ratings) / len(self._ratings)
# Create an instance of the MovieRatings class
movie_ratings = MovieRatings([7, 8, 5, 9, 11, 6, 10])
# Set new ratings with the setter (invalid ratings will be filtered out)
movie_ratings.ratings = [7, 8, 5, 9, 11, -2, 6, 10]
# Access valid ratings and average rating using the getter
print(f"Valid Ratings: {movie_ratings.ratings}") # Output: Valid Ratings: [7, 8, 5, 9, 6, 10]
print(f"Average Rating: {movie_ratings.average_rating}") # Output: Average Rating: 7.5
In this example, the ratings
setter filters out any invalid ratings (less than 0 or greater than 10), ensuring that only valid ratings are stored. The average_rating
getter calculates the average rating based on the filtered ratings. This data analysis example shows how getters and setters can be used for data validation and processing in real-world applications.
Let's consider a data pipeline for image processing. You want to create a ImagePipeline
class that takes a list of images, resizes them, converts them to grayscale, and stores the processed images. We'll use setters to trigger the image processing and getters to access the processed images.
from PIL import Image
class ImagePipeline:
def __init__(self, images):
self._original_images = images
self._processed_images = []
@property
def original_images(self):
return self._original_images
@original_images.setter
def original_images(self, images):
self._original_images = images
self._processed_images = [] # Reset processed images when new images are set
@property
def processed_images(self):
if not self._processed_images:
self._process_images()
return self._processed_images
def _process_images(self):
for image in self._original_images:
resized_image = image.resize((100, 100))
grayscale_image = resized_image.convert("L")
self._processed_images.append(grayscale_image)
# Create an instance of the ImagePipeline class
image1 = Image.open("image1.jpg")
image2 = Image.open("image2.jpg")
image_pipeline = ImagePipeline([image1, image2])
# Set new images with the setter
image3 = Image.open("image3.jpg")
image_pipeline.original_images = [image2, image3]
# Access processed images with the getter (will trigger processing if not already done)
processed_images = image_pipeline.processed_images
In this example, the original_images
setter allows you to set a new list of images for processing. Whenever you set new images, it resets the list of processed images to ensure accurate processing. The processed_images
getter triggers the image processing if the processed images haven't been generated yet. It then returns the list of processed grayscale images.
This data pipeline example demonstrates how getters and setters can be used to manage and automate data processing steps, making the code more modular and organized. It's particularly useful when you need to handle complex data transformations and want to control the data flow through the pipeline.
Let's consider an advanced example of a DataAnalyzer
class that performs statistical analysis on a dataset. The dataset contains student exam scores, and we want to calculate various statistics such as mean, median, and standard deviation. We'll use getters to calculate these statistics on-demand, and setters to update the dataset.
import statistics
class DataAnalyzer:
def __init__(self, data):
self._data = data
@property
def data(self):
return self._data
@data.setter
def data(self, new_data):
if not all(isinstance(score, (int, float)) for score in new_data):
raise ValueError("Data must contain numerical values.")
self._data = new_data
@property
def mean(self):
return statistics.mean(self._data)
@property
def median(self):
return statistics.median(self._data)
@property
def standard_deviation(self):
return statistics.stdev(self._data)
# Create an instance of the DataAnalyzer class
scores = [85, 78, 92, 64, 90]
analyzer = DataAnalyzer(scores)
# Get statistics using the getters
print(f"Original Data: {analyzer.data}") # Output: Original Data: [85, 78, 92, 64, 90]
print(f"Mean Score: {analyzer.mean:.2f}") # Output: Mean Score: 81.80
print(f"Median Score: {analyzer.median}") # Output: Median Score: 85
print(f"Standard Deviation: {analyzer.standard_deviation:.2f}") # Output: Standard Deviation: 10.27
# Update the dataset using the setter
new_scores = [77, 88, 95, 71, 83, 90]
analyzer.data = new_scores
# Get statistics for the updated dataset
print(f"Updated Data: {analyzer.data}") # Output: Updated Data: [77, 88, 95, 71, 83, 90]
print(f"Mean Score: {analyzer.mean:.2f}") # Output: Mean Score: 84.00
print(f"Median Score: {analyzer.median}") # Output: Median Score: 85.5
print(f"Standard Deviation: {analyzer.standard_deviation:.2f}") # Output: Standard Deviation: 7.61
In this example, the data
setter ensures that only numerical values are allowed in the dataset. If you attempt to set non-numerical data, it raises a ValueError
.
The mean
, median
, and standard_deviation
getters calculate the corresponding statistics on-demand when accessed. This approach allows you to perform data analysis only when needed, avoiding unnecessary calculations and improving performance.
Using getters and setters in this data analysis example enables you to manage and analyze datasets with ease, ensuring data integrity and providing quick access to various statistical metrics.