-
Notifications
You must be signed in to change notification settings - Fork 1
/
descriptors.py
117 lines (83 loc) · 2.83 KB
/
descriptors.py
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
"""
Descriptors can be used as class attributes.
They serve two purposes:
1. Allow to control data type of attributes
2. Lazy loading of data https://realpython.com/python-import/#reloading-modules
© Denis Shelemekh, 2020
"""
from typing import Any
def typedproperty(name: str, expected_type: Any) -> Any:
"""
Creates property setter and getter allowing for data type control.
Args:
name: String - name of the property.
expected_type: Any - expected type of the property.
Raises:
TypeError: When value supplied to setter doesn't match expected type.
Usage:
import typedproperty as tp
class Stock:
name = tp.String('name')
shares = tp.Integer('shares')
price = tp.Float('price')
def __init__(self, name: str, shares: int, price: float) -> None:
self.name = name
self.shares = shares
self.price = price
...
"""
private_name = '_' + name
@property
def prop(self) -> Any:
return getattr(self, private_name)
@prop.setter
def prop(self, value: Any) -> None:
if not isinstance(value, expected_type):
raise TypeError(f"Expected {expected_type}")
setattr(self, private_name, value)
return prop
# Shortcuts
String = lambda name: typedproperty(name, str)
Integer = lambda name: typedproperty(name, int)
Float = lambda name: typedproperty(name, float)
# Some other examples of descriptors
class IntegerType:
"""Class for checking if int type attributes are int."""
def __init__(self, name):
self._name = name
def __set__(self, instance, value):
""" SETTER """
if not isinstance(value, int):
raise TypeError(f"{value} is not an int")
else:
instance.__dict__[self._name] = value
def __get__(self, instance, owner):
""" GETTER """
return instance.__dict__[self._name]
class StringType:
"""Class for checking if str type attributes are str."""
def __init__(self, name):
self._name = name
def __set__(self, instance, value):
""" SETTER """
if not isinstance(value, str):
raise TypeError(f"{value} is not a str")
else:
instance.__dict__[self._name] = value
def __get__(self, instance, owner):
""" GETTER """
return instance.__dict__[self._name]
class Car:
"""
Sample class for demonstration of descriptors.
"""
manufacturer = StringType("manufacturer")
model = StringType("model")
year = IntegerType("year")
def __init__(self, manufacturer, model, year):
self.manufacturer = manufacturer
self.model = model
self.year = year
def __str__(self):
return f"Manufacturer: {self.manufacturer}, model: {self.model}, " \
f"year: {self.year}"