Property Decorators متقدمة
جزء من قسم Object Oriented Programming في رحلة Python على Coddy — الدرس 31 من 64.
توفر مزخرفات الخصائص المتقدمة تحكماً أكثر تطوراً في الوصول إلى السمات، بما في ذلك الخصائص المحسوبة، والحاذفات (deleters)، وإدارة الخصائص الكاملة.
إليك مثال على الخصائص المحسوبة (computed properties) التي تشتق قيمها من سمات أخرى:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
return self.width * self.height
@property
def perimeter(self):
return 2 * (self.width + self.height)استخدم الخصائص المحسوبة (computed properties) مثل السمات العادية:
rect = Rectangle(5, 3)
print(rect.area) # 15 - يتم حسابها تلقائياً
print(rect.perimeter) # 16 - يتم حسابها تلقائياًأنشئ خاصية باستخدام getter و setter و deleter:
class Temperature:
def __init__(self):
self._temp = 0
@property
def temperature(self):
return self._temp
@temperature.setter
def temperature(self, value):
if value < -273.15:
raise ValueError("Temperature below absolute zero!")
self._temp = value
@temperature.deleter
def temperature(self):
print("Resetting temperature to 0")
self._temp = 0استخدم وظائف الخاصية الكاملة:
temp = Temperature()استخدم دالة التعيين (setter) مع التحقق من الصحة:
temp.temperature = 25
print(temp.temperature) # 25
# temp.temperature = -300 # سيؤدي إلى رفع ValueErrorاستخدم الـ deleter:
del temp.temperature
print(temp.temperature) # 0قم بإنشاء مثال أكثر تعقيداً يتضمن نتيجة لعبة:
class Player:
def __init__(self, name):
self.name = name
self._score = 0
self._level = 1
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if value >= 0:
self._score = value
self._level = (value // 1000) + 1
else:
raise ValueError("Score cannot be negative")
@score.deleter
def score(self):
print(f"Resetting {self.name}'s progress")
self._score = 0
self._level = 1
@property
def level(self):
return self._level
player = Player("Alice")
player.score = 2500
print(f"Score: {player.score}, Level: {player.level}") # Score: 2500, Level: 3
del player.score
print(f"Score: {player.score}, Level: {player.level}") # Score: 0, Level: 1المخرجات:
15
16
25
Resetting temperature to 0
0
Score: 2500, Level: 3
Resetting Alice's progress
Score: 0, Level: 1نقطة رئيسية: تسمح مزخرفات الخصائص (property decorators) المتقدمة بالخصائص المحسوبة (المحسوبة من بيانات أخرى)، وحذف الخصائص باستخدام @property.deleter، والتحكم الكامل في جلب وتعيين وحذف السمات. هذا ينشئ واجهات بديهية مع الحفاظ على التحقق القوي من صحة البيانات والكبسلة (encapsulation).
التحدي
متوسطفي هذا التحدي، ستقوم بتنفيذ فئة Rectangle مع التغليف (encapsulation) المناسب والتحقق من صحة الخصائص.
rectangle.py- هذا هو الملف الذي تحتاج إلى تعديله، ويحتوي على تعليقات TODO لتوجيه عملية التنفيذ الخاصة بكdriver.py- يحتوي على سيناريوهات اختبار شاملة (لا تقم بتعديله)
- قم بتنفيذ سمات خاصة (private attributes) لـ
_widthو_height - أنشئ خصائص (properties) لـ
widthوheightمع التحقق من الصحة (يجب أن تكون موجبة)- قم برفع رسائل ValueError المناسبة كما هو محدد في تعليقات TODO
- هام: تحقق مما إذا كانت القيمة أقل من أو تساوي 0، ثم ارفع الخطأ (هذا يضمن سلوكاً متسقاً)
- قم بتنفيذ خصائص للقراءة فقط (read-only properties) لـ
areaوperimeter - أنشئ خاصية
dimensionsمع وظائف getter و setter و deleter كما هو موضح في تعليقات TODO- هام: في الـ setter، استخدم صيغة تفكيك الصف (tuple unpacking):
width, height = dimensions
- هام: في الـ setter، استخدم صيغة تفكيك الصف (tuple unpacking):
ورقة مرجعية
توفر مزخرفات الخصائص (property decorators) المتقدمة تحكماً متطوراً في الوصول إلى السمات باستخدام الخصائص المحسوبة، والحاذفات (deleters)، وإدارة الخصائص الكاملة.
الخصائص المحسوبة:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
return self.width * self.height
@property
def perimeter(self):
return 2 * (self.width + self.height)خاصية كاملة مع الجالب (Getter)، والمُعيّن (Setter)، والحاذف (Deleter):
class Temperature:
def __init__(self):
self._temp = 0
@property
def temperature(self):
return self._temp
@temperature.setter
def temperature(self, value):
if value < -273.15:
raise ValueError("Temperature below absolute zero!")
self._temp = value
@temperature.deleter
def temperature(self):
print("Resetting temperature to 0")
self._temp = 0الاستخدام:
# الخصائص المحسوبة
rect = Rectangle(5, 3)
print(rect.area) # 15
print(rect.perimeter) # 16
# وظائف الخاصية الكاملة
temp = Temperature()
temp.temperature = 25 # يستخدم المُعيّن (setter)
print(temp.temperature) # يستخدم الجالب (getter)
del temp.temperature # يستخدم الحاذف (deleter)جرّب بنفسك
from rectangle import Rectangle
# معالج حالات الاختبار
test_case = input()
# اختبار الوظائف الأساسية
if test_case == "default_test":
rect = Rectangle(5, 3)
print(f"Width: {rect.width}, Height: {rect.height}")
print(f"Area: {rect.area}, Perimeter: {rect.perimeter}")
# اختبار خاصية الأبعاد (dimensions)
print(f"Dimensions: {rect.dimensions}")
rect.dimensions = (10, 8)
print(f"New area: {rect.area}")
# اختبار التحقق من الصحة
try:
rect.width = -2
except ValueError as e:
print(f"Validation error: {e}")
# اختبار الحاذف (deleter)
del rect.dimensions
print(f"After reset: {rect.dimensions}")
# الاختبار بقيم صفرية
elif test_case == "zero_values":
try:
rect = Rectangle(0, 5)
except ValueError as e:
print(f"Error creating rectangle: {e}")
try:
rect = Rectangle(5, 0)
except ValueError as e:
print(f"Error creating rectangle: {e}")
# الاختبار بقيم سالبة
elif test_case == "negative_values":
rect = Rectangle(5, 3)
original_dimensions = rect.dimensions
try:
rect.dimensions = (5, -3)
except ValueError as e:
print(f"Error setting dimensions: {e}")
print(f"Dimensions after failed update: {rect.dimensions}")
print(f"Original dimensions preserved: {rect.dimensions == original_dimensions}")
# الاختبار بقيم كبيرة
elif test_case == "large_values":
rect = Rectangle(1000000, 2000000)
print(f"Large rectangle area: {rect.area}")
print(f"Large rectangle perimeter: {rect.perimeter}")
# الاختبار بقيم عشرية
elif test_case == "float_values":
rect = Rectangle(3.5, 2.75)
print(f"Dimensions: {rect.dimensions}")
print(f"Area: {rect.area}")
print(f"Perimeter: {rect.perimeter}")
rect.dimensions = (1.1, 2.2)
print(f"New area with float dimensions: {rect.area}")
# الاختبار مع أخطاء النوع
elif test_case == "type_errors":
try:
rect = Rectangle("5", 3)
except Exception as e:
print(f"Type error during creation: {type(e).__name__}: {e}")
rect = Rectangle(5, 3)
try:
rect.dimensions = 10 # ليس صفاً (tuple)
except Exception as e:
print(f"Type error setting dimensions: {type(e).__name__}: {e}")
# اختبار عمليات الخصائص المتعددة
elif test_case == "property_operations":
rect = Rectangle(5, 10)
print(f"Initial - Width: {rect.width}, Height: {rect.height}, Area: {rect.area}")
rect.width = 8
print(f"After width change - Width: {rect.width}, Area: {rect.area}")
rect.height = 6
print(f"After height change - Height: {rect.height}, Area: {rect.area}")
rect.dimensions = (12, 9)
print(f"After dimensions change - Dimensions: {rect.dimensions}, Area: {rect.area}")
del rect.dimensions
print(f"After reset - Dimensions: {rect.dimensions}, Area: {rect.area}")
# اختبار الحالات الحدية للتحقق من صحة الخصائص
elif test_case == "validation_edge_cases":
rect = Rectangle(5, 3)
try:
rect.width = 0
except ValueError as e:
print(f"Zero width error: {e}")
# يجب قبول القيمة الموجبة الصغيرة جداً
rect.height = 0.0001
print(f"Small height accepted: {rect.height}")
print(f"Area with small height: {rect.area}")
# اختبار مستطيلات متعددة
elif test_case == "multiple_rectangles":
rect1 = Rectangle(5, 3)
rect2 = Rectangle(10, 2)
rect3 = Rectangle(4, 4)
print(f"Rectangle 1 - Area: {rect1.area}, Perimeter: {rect1.perimeter}")
print(f"Rectangle 2 - Area: {rect2.area}, Perimeter: {rect2.perimeter}")
print(f"Rectangle 3 - Area: {rect3.area}, Perimeter: {rect3.perimeter}")
# تعديل كل مستطيل
rect1.width = 7
rect2.height = 5
rect3.dimensions = (6, 6)
print(f"After modifications:")
print(f"Rectangle 1 - Dimensions: {rect1.dimensions}")
print(f"Rectangle 2 - Dimensions: {rect2.dimensions}")
print(f"Rectangle 3 - Dimensions: {rect3.dimensions}")
# اختبار الأداء مع العديد من العمليات
elif test_case == "performance_test":
rect = Rectangle(5, 5)
# إجراء العديد من عمليات الخصائص
for i in range(1000):
rect.width = i % 10 + 1 # القيم 1-10
rect.height = i % 5 + 1 # القيم 1-5
area = rect.area # الوصول إلى خاصية محسوبة
perimeter = rect.perimeter # الوصول إلى خاصية محسوبة
dims = rect.dimensions # الوصول إلى جالب الخاصية (getter)
print(f"Final state after 1000 operations:")
print(f"Width: {rect.width}, Height: {rect.height}")
print(f"Area: {rect.area}, Perimeter: {rect.perimeter}")يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات OOP
الملفات الخارجيةمقدمة في OOPClasses مقابل Objectsمعامل selfMethodsAttributesدالة البناء (__init__)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسيةدالة ()superإعادة تعريف الدوال (Method Overriding)الوراثة المتعددةترتيب استدعاء الدوال (Method Resolution Order)مراجعة - هيكلية الموظفين5تعدد الأشكال
مراجعة إعادة تعريف الدوالمفهوم Duck Typingالأصناف والدوال المجردةتصميم الواجهاتملخص - حاسبة الأشكال3خصائص الـ Class
متغيرات الـ Instance مقابل متغيرات الـ ClassProperty Decoratorsالسمات الخاصةمراجعة - مدير الحساب البنكي6التغليف (Encapsulation)
الأعضاء العامة والمحمية والخاصةمحددات الوصولإخفاء المعلوماتProperty Decorators متقدمةمراجعة - نظام سجلات الطلاب