-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexamples.py
More file actions
100 lines (80 loc) · 4.06 KB
/
Copy pathexamples.py
File metadata and controls
100 lines (80 loc) · 4.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
"""
Chapter 23: Attribute Descriptors
=================================
Original implementations exploring the Descriptor Protocol.
Descriptors abstract away @property methods into reusable classes,
allowing us to validate data across multiple attributes seamlessly.
Key concepts covered:
- Overriding Descriptors (__set__)
- The __set_name__ callback (Python 3.6+)
- Safe instance storage (__dict__)
"""
import sys
sys.stdout.reconfigure(encoding="utf-8")
def section(title: str) -> None:
print(f"\n{'=' * 60}\n=== {title}\n{'=' * 60}")
# ─────────────────────────────────────────────────────────────────────────────
# 1. The Quantity Descriptor (Overriding)
# ─────────────────────────────────────────────────────────────────────────────
class Quantity:
"""
A Descriptor that ensures a value is > 0.
Because it implements __set__, it is an 'Overriding Descriptor'.
"""
def __set_name__(self, owner, name):
"""
Called when the managed class is created.
owner: The managed class (LineItem)
name: The name the descriptor was assigned to ('weight' or 'price')
"""
self.storage_name = name
def __set__(self, instance, value):
"""
Intercepts assignment: obj.weight = value
instance: The managed instance (a specific LineItem)
"""
if value > 0:
# We MUST store the value in the managed instance's dictionary!
# If we store it on `self` (the descriptor), it becomes global.
instance.__dict__[self.storage_name] = value
else:
raise ValueError(f"{self.storage_name} must be > 0")
def __get__(self, instance, owner):
"""
Intercepts reading: x = obj.weight
"""
if instance is None:
# If accessed via the class (LineItem.weight), return the descriptor
return self
return instance.__dict__[self.storage_name]
# ─────────────────────────────────────────────────────────────────────────────
# 2. The Managed Class
# ─────────────────────────────────────────────────────────────────────────────
class LineItem:
# We instantiate the descriptors as CLASS attributes.
weight = Quantity()
price = Quantity()
def __init__(self, description, weight, price):
self.description = description
# These assignments trigger Quantity.__set__
self.weight = weight
self.price = price
def subtotal(self):
# These reads trigger Quantity.__get__
return self.weight * self.price
def demo_descriptors() -> None:
section("Part 1: Attribute Descriptors")
item = LineItem("Truffles", 1.5, 100.0)
print(f" Item: {item.description}")
print(f" Weight: {item.weight} | Price: {item.price}")
print(f" Subtotal: {item.subtotal()}")
print("\n Attempting to set invalid price...")
try:
item.price = -50.0
except ValueError as e:
print(f" Caught expected error: {e}")
# ─────────────────────────────────────────────────────────────────────────────
# MAIN
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
demo_descriptors()