-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdescriptors.py
More file actions
163 lines (121 loc) · 4.59 KB
/
descriptors.py
File metadata and controls
163 lines (121 loc) · 4.59 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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# https://dev.to/dawranliou/writing-descriptors-in-python-36
# https://developer.ibm.com/tutorials/os-pythondescriptors/
# A descriptor is an object attribute with “binding behavior”,
# one whose attribute access has been overridden by methods in the descriptor protocol.
# Those methods are __get__(), __set__(), and __delete__().
# If any of those methods are defined for an object, it is said to be a descriptor.
# Why descriptors
class Order:
def __init__(self, name, price, quantity):
self.name = name
self.price = price
self.quantity = quantity
def total(self):
return self.price * self.quantity
apple_order = Order('apple', 1, 10)
print(apple_order.total())
# Bug
apple_order.quantity = -10
print(apple_order.total())
# Instead of using getter and setter methods and break the APIs,
# use property to enforce quantity be positive.
class Order:
def __init__(self, name, price, quantity):
self._name = name
self.price = price # price attribute cannot be negative neither.
self._quantity = quantity
@property
def quantity(self):
return self._quantity
@quantity.setter
def quantity(self, value):
if value < 0:
raise ValueError('Cannot be negative.')
self._quantity = value
def total(self):
return self.price * self.quantity
apple_order = Order('apple', 1, 10)
try:
apple_order.quantity = -10
except ValueError:
print("Raised exeception")
print(apple_order.total())
# 'price' attribute cannot be negative neither. It might be attempting to just create another property for price,
# but remember the DRY principle. Also, in our example, there might be more attributes need to be added into this class in the future.
# Use descriptors, How to write descriptors?
# Define the NonNegative class and implement the descriptor protocols.
class NonNegative:
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
if value < 0:
raise ValueError('Cannot be negative.')
instance.__dict__[self.name] = value
# The main thing to remember is that descriptors are linked to classes and not to instances.
class Order:
price = NonNegative('price') # Similar to SQLAlchemy
quantity = NonNegative('quantity')
def __init__(self, name, price, quantity):
self._name = name
self.price = price
self.quantity = quantity
def total(self):
return self.price * self.quantity
# when we go to set price/quantity, Python notices that it is a descriptor.
# Python knows that price/quantity is a descriptor because we defined it as such when we created it as a class attribute.
# So when we go to set price/quantity, we actually call our descriptor’s __set__ method which
# passes in the instance and the value that we are trying to set.
apple_order = Order('apple', 1, 10)
apple_order.total()
try:
apple_order.price = -10
except ValueError:
print("Raised exeception")
try:
apple_order.quantity = -10
except ValueError:
print("Raised exeception")
# In Python 3.6
# object.__set_name__(self, owner, name)
# Called at the time the owning class owner is created. The descriptor has been assigned to name.
class NonNegative:
def __get__(self, instance, owner): # Instance of the Order class, owner is the Order class
return instance.__dict__[self.name]
def __set__(self, instance, value):
print("In set ",instance,value)
if value < 0:
raise ValueError('Cannot be negative.')
instance.__dict__[self.name] = value
# Called automatically with the name of the attribute, on the LHS. [price,quantity]
def __set_name__(self, owner, name):
print("__set_name__=", name)
self.name = name
class Order:
price = NonNegative()
quantity = NonNegative()
def __init__(self, name, price, quantity):
self._name = name
self.price = price
self.quantity = quantity
def total(self):
return self.price * self.quantity
# def __setattr__(self, key, value):
# print("In setter",key,value)
# self.__dict__[key] = value
apple_order = Order('apple', 1, 10)
apple_order.total()
apple_order.price = 5
try:
apple_order.price = -10
except ValueError:
print("Raised exeception")
try:
apple_order.quantity = -10
except ValueError:
print("Raised exeception")
print("\n\n********Order.__dict__********")
print(Order.__dict__)
print("\n\n********apple_order.__dict__********")
print(apple_order.__dict__)