Repository navigation
Expand file tree
/
Copy pathproperty.py
More file actions
98 lines (77 loc) 路 3.18 KB
/
Copy pathproperty.py
File metadata and controls
98 lines (77 loc) 路 3.18 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
"""
Properties provide a Pythonic mechanism for managed attributes.
In many object-oriented languages, developers write getter and setter methods
(e.g., `get_balance()` and `set_balance()`) to protect private variables.
In Python, this is considered unidiomatic because public attributes are preferred
until validation or dynamic computation is needed.
The `@property` decorator allows a method to be accessed like a standard
attribute. Pair it with `@<attribute>.setter` to validate incoming values
and `@<attribute>.deleter` to control attribute deletion without altering
the public API of your class.
"""
# Module-level constants
_ABSOLUTE_ZERO_CELSIUS = -273.15
_DEFAULT_CELSIUS = 0.0
class Temperature:
"""Temperature representation supporting Celsius and Fahrenheit scales."""
def __init__(self, celsius: float = _DEFAULT_CELSIUS) -> None:
self.celsius = celsius
@property
def celsius(self) -> float:
"""Get the current temperature in Celsius."""
return self._celsius
@celsius.setter
def celsius(self, value: float) -> None:
"""Set the temperature in Celsius with physical lower-bound validation."""
if value < _ABSOLUTE_ZERO_CELSIUS:
raise ValueError(f"Temperature cannot be below {_ABSOLUTE_ZERO_CELSIUS}掳C")
self._celsius = float(value)
@celsius.deleter
def celsius(self) -> None:
"""Reset temperature to default value when deleted."""
self._celsius = _DEFAULT_CELSIUS
@property
def fahrenheit(self) -> float:
"""Compute temperature in Fahrenheit on demand."""
return round((self._celsius * 9.0 / 5.0) + 32.0, 2)
@fahrenheit.setter
def fahrenheit(self, value: float) -> None:
"""Set temperature using Fahrenheit, updating Celsius internally."""
self.celsius = round((value - 32.0) * 5.0 / 9.0, 2)
@property
def is_freezing(self) -> bool:
"""Read-only property checking whether the temperature is at or below freezing."""
return self._celsius <= 0.0
def main() -> None:
# 1. Basic property access behaves like a regular attribute
temp = Temperature(25.0)
assert temp.celsius == 25.0
# 2. Computed property is derived on-the-fly
assert temp.fahrenheit == 77.0
assert not temp.is_freezing
# 3. Setter handles dynamic conversion across units
temp.fahrenheit = 32.0
assert temp.celsius == 0.0
assert temp.is_freezing
# 4. Setter enforces validation rules
temp.celsius = -100.0
assert temp.is_freezing
invalid_celsius_failed = False
try:
temp.celsius = -300.0
except ValueError as e:
invalid_celsius_failed = True
assert str(e) == f"Temperature cannot be below {_ABSOLUTE_ZERO_CELSIUS}掳C"
assert invalid_celsius_failed is True
# 5. Read-only properties cannot be overwritten directly
readonly_overwrite_failed = False
try:
temp.is_freezing = True # type: ignore[misc]
except AttributeError:
readonly_overwrite_failed = True
assert readonly_overwrite_failed is True
# 6. Deleter hooks into attribute deletion syntax
del temp.celsius
assert temp.celsius == _DEFAULT_CELSIUS
if __name__ == "__main__":
main()