-
Notifications
You must be signed in to change notification settings - Fork 32
Expand file tree
/
Copy path54.descriptors.py
More file actions
87 lines (65 loc) · 2.51 KB
/
Copy path54.descriptors.py
File metadata and controls
87 lines (65 loc) · 2.51 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
"""Short descriptor examples with human-like comments.
Shows a Typed data descriptor and a simple cached property.
"""
from typing import Any
class Typed:
"""Data descriptor that enforces the value type.
We store the value in the instance.__dict__ under the provided name.
This raises on wrong types so callers get early feedback.
"""
def __init__(self, name: str, expected_type: type) -> None:
self.name = name
self.expected_type = expected_type
def __set_name__(self, owner, name):
# saves the attribute name if the class assigns a different one
self.public_name = name
def __get__(self, instance: Any, owner: type = None) -> Any:
# when accessed on the class, return the descriptor itself
if instance is None:
return self
return instance.__dict__.get(self.name)
def __set__(self, instance: Any, value: Any) -> None:
# enforce the expected type
if not isinstance(value, self.expected_type):
raise TypeError(f"{self.name} must be {self.expected_type.__name__}")
instance.__dict__[self.name] = value
class CachedProperty:
"""A tiny cached-property: compute once, then store on the instance.
This is a non-data descriptor (no __set__), so assigning the cached name
on the instance bypasses future descriptor lookups.
"""
def __init__(self, func):
self.func = func
self.__doc__ = getattr(func, '__doc__')
def __get__(self, instance, owner=None):
if instance is None:
return self
value = self.func(instance)
instance.__dict__[self.func.__name__] = value
return value
class Point:
# descriptors: declare at the class level
x = Typed('x', int)
y = Typed('y', int)
def __init__(self, x: int, y: int) -> None:
# these assignments go through Typed.__set__
self.x = x
self.y = y
@CachedProperty
def hypot(self):
# small computation that we'd like to cache
print('computing hypot')
from math import hypot
return hypot(self.x, self.y)
if __name__ == '__main__':
p = Point(3, 4)
print('x, y:', p.x, p.y)
# wrong type assignment shows the guard
try:
p.x = 2.5
except TypeError as e:
print('caught type error:', e)
# cached property: first access computes, second returns cached value
print('first hypot:', p.hypot)
print('second hypot (cached):', p.hypot)
print('instance keys:', list(p.__dict__.keys()))